aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Tolmachev <me@igorek.dev>2024-06-15 03:30:50 +0900
committerIgor Tolmachev <me@igorek.dev>2024-06-23 15:34:34 +0900
commitf8c3c93824645a807d28b760855b4676ea479720 (patch)
tree1f91838c2abcb3b0683a061f892b8e2835be4fa1
parentbd77f62e99a5300dfa52aef3a7040414b28ebfd6 (diff)
downloadarchivator-f8c3c93824645a807d28b760855b4676ea479720.tar.gz
archivator-f8c3c93824645a807d28b760855b4676ea479720.zip
Add simple zip reader
-rw-r--r--.vscode/settings.json7
-rw-r--r--Cargo.toml2
-rw-r--r--src/archive.rs20
-rw-r--r--src/driver/driver.rs25
-rw-r--r--src/driver/file.rs1
-rw-r--r--src/driver/mod.rs5
-rw-r--r--src/error.rs35
-rw-r--r--src/lib.rs10
-rw-r--r--src/structs/de.rs0
-rw-r--r--src/structs/mod.rs2
-rw-r--r--src/structs/ser.rs6
-rw-r--r--src/zip/driver.rs143
-rw-r--r--src/zip/error.rs58
-rw-r--r--src/zip/file.rs45
-rw-r--r--src/zip/mod.rs7
-rw-r--r--src/zip/structs.rs52
-rw-r--r--tests/usage.rs19
17 files changed, 434 insertions, 3 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c36ecad..193c892 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,11 +1,12 @@
1{ 1{
2 "cSpell.words": [ 2 "cSpell.words": [
3 "archivator", 3 "archivator",
4 "bincode",
4 "chrono", 5 "chrono",
5 "datatypes", 6 "datatypes",
6 "eocd", 7 "datetime",
7 "LZMA", 8 "eocdr",
8 "Zstandard" 9 "rposition"
9 ], 10 ],
10 "rust-analyzer.linkedProjects": ["./Cargo.toml", "./Cargo.toml"] 11 "rust-analyzer.linkedProjects": ["./Cargo.toml", "./Cargo.toml"]
11} 12}
diff --git a/Cargo.toml b/Cargo.toml
index 93d39e4..d511387 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,4 +12,6 @@ categories = ["compression", "filesystem"]
12exclude = [".vscode/"] 12exclude = [".vscode/"]
13 13
14[dependencies] 14[dependencies]
15bincode = "1.3.3"
15chrono = "0.4.29" 16chrono = "0.4.29"
17serde = { version = "1.0.203", features = ["derive"] }
diff --git a/src/archive.rs b/src/archive.rs
new file mode 100644
index 0000000..a422f9e
--- /dev/null
+++ b/src/archive.rs
@@ -0,0 +1,20 @@
1use crate::driver::{ArchiveRead, ArchiveWrite, Driver};
2use crate::ArchiveResult;
3use std::io::{Read, Write};
4
5pub struct Archive<D: Driver> {
6 pub(crate) driver: D,
7}
8
9impl<D: ArchiveRead> Archive<D>
10where
11 D::IO: std::io::Read,
12{
13 pub fn new(io: D::IO) -> ArchiveResult<Self, D::Error> {
14 Ok(Self {
15 driver: D::read(io)?,
16 })
17 }
18}
19
20impl<D: ArchiveWrite> Archive<D> where D::IO: Read + Write {}
diff --git a/src/driver/driver.rs b/src/driver/driver.rs
new file mode 100644
index 0000000..3a8ed16
--- /dev/null
+++ b/src/driver/driver.rs
@@ -0,0 +1,25 @@
1use crate::driver::ArchiveFile;
2use crate::ArchiveResult;
3use std::error::Error;
4use std::io::{Read, Write};
5
6pub trait Driver: Sized {
7 type Error: Error;
8
9 type IO;
10 type File: ArchiveFile;
11}
12
13pub trait ArchiveRead: Driver
14where
15 Self::IO: Read,
16{
17 // Create driver instance
18 fn read(io: Self::IO) -> ArchiveResult<Self, Self::Error>;
19}
20
21pub trait ArchiveWrite: ArchiveRead
22where
23 Self::IO: Read + Write,
24{
25}
diff --git a/src/driver/file.rs b/src/driver/file.rs
new file mode 100644
index 0000000..a4974f3
--- /dev/null
+++ b/src/driver/file.rs
@@ -0,0 +1 @@
pub trait ArchiveFile {}
diff --git a/src/driver/mod.rs b/src/driver/mod.rs
new file mode 100644
index 0000000..36ee6b5
--- /dev/null
+++ b/src/driver/mod.rs
@@ -0,0 +1,5 @@
1mod driver;
2mod file;
3
4pub use driver::{ArchiveRead, ArchiveWrite, Driver};
5pub use file::ArchiveFile;
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..6d7aba4
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,35 @@
1use std::error::Error;
2use std::fmt::Display;
3use std::io;
4
5pub type ArchiveResult<R, E> = Result<R, ArchiveError<E>>;
6
7#[derive(Debug)]
8pub enum ArchiveError<E: Error> {
9 IO(io::Error),
10 Driver { name: &'static str, error: E },
11}
12
13impl<E: Error> From<io::Error> for ArchiveError<E> {
14 fn from(value: io::Error) -> Self {
15 Self::IO(value)
16 }
17}
18
19impl<E: Error> Display for ArchiveError<E> {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 ArchiveError::IO(error) => write!(f, "{error}"),
23 ArchiveError::Driver { name, error } => write!(f, "{name}: {error}"),
24 }
25 }
26}
27
28impl<E: Error> Error for ArchiveError<E> {
29 fn source(&self) -> Option<&(dyn Error + 'static)> {
30 match self {
31 Self::IO(error) => Some(error),
32 _ => None,
33 }
34 }
35}
diff --git a/src/lib.rs b/src/lib.rs
index e69de29..236c58d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -0,0 +1,10 @@
1mod archive;
2mod error;
3mod structs;
4
5pub mod driver;
6pub mod zip;
7
8pub use archive::Archive;
9pub use error::{ArchiveError, ArchiveResult};
10pub use zip::Zip;
diff --git a/src/structs/de.rs b/src/structs/de.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/structs/de.rs
diff --git a/src/structs/mod.rs b/src/structs/mod.rs
new file mode 100644
index 0000000..d7e9daa
--- /dev/null
+++ b/src/structs/mod.rs
@@ -0,0 +1,2 @@
1mod de;
2mod ser;
diff --git a/src/structs/ser.rs b/src/structs/ser.rs
new file mode 100644
index 0000000..a3fe291
--- /dev/null
+++ b/src/structs/ser.rs
@@ -0,0 +1,6 @@
1use crate::ArchiveError;
2use serde::{ser, Serialize};
3
4pub struct ArchiveSerializer {
5 bin: Vec<u8>,
6}
diff --git a/src/zip/driver.rs b/src/zip/driver.rs
new file mode 100644
index 0000000..733d44b
--- /dev/null
+++ b/src/zip/driver.rs
@@ -0,0 +1,143 @@
1use crate::driver::{ArchiveRead, ArchiveWrite, Driver};
2use crate::zip::error::{ZipError, ZipResult};
3use crate::zip::structs::{EOCDR64Locator, CDR, EOCDR, EOCDR64};
4use crate::zip::ZipFile;
5use std::collections::HashMap as Map;
6use std::fs::File;
7use std::io::{Read, Seek, SeekFrom, Write};
8
9pub struct Zip<IO = File> {
10 io: IO,
11
12 files: Map<String, ZipFile>,
13 comment: String,
14}
15
16impl<IO> Driver for Zip<IO> {
17 type Error = ZipError;
18
19 type IO = IO;
20 type File = ZipFile;
21}
22
23impl<IO: Read + Seek> ArchiveRead for Zip<IO> {
24 fn read(mut io: Self::IO) -> ZipResult<Self> {
25 // Search eocdr
26 let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64;
27 let start = io.seek(SeekFrom::End(-limit))?;
28 let pos = start + {
29 let mut buf = vec![0; limit as usize];
30 io.read(&mut buf)?;
31 buf[..buf.len() - 18]
32 .windows(4)
33 .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50)
34 .ok_or(ZipError::EOCDRNotFound)? as u64
35 };
36
37 // Read eocdr
38 io.seek(SeekFrom::Start(pos + 4))?;
39 let buf = {
40 let mut buf = [0; 18];
41 io.read(&mut buf)?;
42 buf
43 };
44 let eocdr: EOCDR = bincode::deserialize(&buf).map_err(|_| ZipError::InvalidEOCDR)?;
45 let comment = {
46 let mut buf = vec![0; eocdr.comment_len as usize];
47 io.read(&mut buf)?;
48 String::from_utf8(buf).map_err(|_| ZipError::InvalidArchiveComment)?
49 };
50
51 // Try to find eocdr64locator
52 io.seek(SeekFrom::Start(pos - 20))?;
53 let buf = {
54 let mut buf = [0; 20];
55 io.read(&mut buf)?;
56 buf
57 };
58 let (cd_pointer, cd_size, cd_records) =
59 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 {
60 let eocdr64locator: EOCDR64Locator =
61 bincode::deserialize(&buf[4..]).map_err(|_| ZipError::InvalidEOCDR64Locator)?;
62
63 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?;
64 let buf = {
65 let mut buf = [0; 56];
66 io.read(&mut buf)?;
67 buf
68 };
69 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 {
70 return Err(ZipError::InvalidEOCDR64Signature.into());
71 }
72 let eocdr64: EOCDR64 =
73 bincode::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
84 // Read cd records
85 let mut files = Map::with_capacity(cd_records as usize);
86 io.seek(SeekFrom::Start(cd_pointer))?;
87 let buf = {
88 let mut buf = vec![0; cd_size as usize];
89 io.read(&mut buf)?;
90 buf
91 };
92 let mut records = buf.as_slice();
93
94 for _ in 0..cd_records {
95 let buf = {
96 let mut buf = [0; 46];
97 records.read(&mut buf)?;
98 buf
99 };
100
101 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x02014b50 {
102 return Err(ZipError::InvalidCDRSignature.into());
103 }
104 let cdr: CDR = bincode::deserialize(&buf[4..]).map_err(|_| ZipError::InvalidCDR)?;
105 let name = {
106 let mut buf = vec![0; cdr.name_len as usize];
107 records.read(&mut buf)?;
108 String::from_utf8(buf).map_err(|_| ZipError::InvalidFileName)?
109 };
110 let extra_fields = {
111 let mut buf = vec![0; cdr.extra_field_len as usize];
112 records.read(&mut buf)?;
113 buf
114 };
115 let comment = {
116 let mut buf = vec![0; cdr.comment_len as usize];
117 records.read(&mut buf)?;
118 String::from_utf8(buf).map_err(|_| ZipError::InvalidFileComment)?
119 };
120
121 files.insert(
122 name.clone(),
123 ZipFile::new(
124 name,
125 cdr.dos_date,
126 cdr.dos_time,
127 cdr.compression_method,
128 cdr.compressed_size as u64,
129 cdr.size as u64,
130 comment,
131 ),
132 );
133 }
134
135 Ok(Self { io, files, comment })
136 }
137}
138
139impl<IO: Read + Seek> Zip<IO> {}
140
141impl<IO: Read + Write + Seek> ArchiveWrite for Zip<IO> {}
142
143impl<IO: Read + Write> Zip<IO> {}
diff --git a/src/zip/error.rs b/src/zip/error.rs
new file mode 100644
index 0000000..ad1989a
--- /dev/null
+++ b/src/zip/error.rs
@@ -0,0 +1,58 @@
1use crate::{ArchiveError, ArchiveResult};
2use std::error::Error;
3use std::fmt::Display;
4
5pub type ZipResult<R> = ArchiveResult<R, ZipError>;
6
7#[derive(Debug)]
8pub enum ZipError {
9 EOCDRNotFound,
10 InvalidEOCDR,
11 InvalidArchiveComment,
12
13 InvalidEOCDR64Locator,
14 InvalidEOCDR64Signature,
15 InvalidEOCDR64,
16
17 InvalidCDRSignature,
18 InvalidCDR,
19 InvalidFileName,
20 InvalidFileComment,
21}
22
23impl From<ZipError> for ArchiveError<ZipError> {
24 fn from(value: ZipError) -> Self {
25 return ArchiveError::Driver {
26 name: "Zip",
27 error: value,
28 };
29 }
30}
31
32impl Display for ZipError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 ZipError::EOCDRNotFound => write!(f, "End of central directory record not found"),
36 ZipError::InvalidEOCDR => write!(f, "Invalid end of central directory record"),
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!(
43 f,
44 "Invalid signature of zip64 end of central directory record"
45 )
46 }
47 ZipError::InvalidEOCDR64 => write!(f, "Invalid zip64 end of central directory record"),
48 ZipError::InvalidCDRSignature => {
49 write!(f, "Invalid signature of central directory record")
50 }
51 ZipError::InvalidCDR => write!(f, "Invalid central directory record"),
52 ZipError::InvalidFileName => write!(f, "Invalid file name"),
53 ZipError::InvalidFileComment => write!(f, "Invalid file comment"),
54 }
55 }
56}
57
58impl Error for ZipError {}
diff --git a/src/zip/file.rs b/src/zip/file.rs
new file mode 100644
index 0000000..3b63c2a
--- /dev/null
+++ b/src/zip/file.rs
@@ -0,0 +1,45 @@
1use crate::driver::ArchiveFile;
2use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
3
4pub struct ZipFile {
5 pub name: String,
6 pub datetime: NaiveDateTime,
7 pub compression_method: u16,
8 pub compressed_size: u64,
9 pub size: u64,
10 pub comment: String,
11}
12
13impl ArchiveFile for ZipFile {}
14
15impl ZipFile {
16 pub fn new(
17 name: String,
18 dos_date: u16,
19 dos_time: u16,
20 compression_method: u16,
21 compressed_size: u64,
22 size: u64,
23 comment: String,
24 ) -> 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 {
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,
40 compressed_size,
41 size,
42 comment,
43 }
44 }
45}
diff --git a/src/zip/mod.rs b/src/zip/mod.rs
new file mode 100644
index 0000000..612a946
--- /dev/null
+++ b/src/zip/mod.rs
@@ -0,0 +1,7 @@
1mod driver;
2mod error;
3mod file;
4mod structs;
5
6pub use driver::Zip;
7pub use file::ZipFile;
diff --git a/src/zip/structs.rs b/src/zip/structs.rs
new file mode 100644
index 0000000..e38f9f0
--- /dev/null
+++ b/src/zip/structs.rs
@@ -0,0 +1,52 @@
1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize)]
4pub struct EOCDR {
5 pub eocdr_disk: u16,
6 pub cd_disk: u16,
7 pub cd_disk_records: u16,
8 pub cd_records: u16,
9 pub cd_size: u32,
10 pub cd_pointer: u32,
11 pub comment_len: u16,
12}
13
14#[derive(Serialize, Deserialize)]
15pub struct EOCDR64Locator {
16 pub eocdr64_disk: u32,
17 pub eocdr64_pointer: u64,
18 pub disks: u32,
19}
20
21#[derive(Serialize, Deserialize)]
22pub struct EOCDR64 {
23 pub eocdr64_size: u64,
24 pub version: u16,
25 pub version_needed: u16,
26 pub eocdr64_disk: u32,
27 pub cd_disk: u32,
28 pub cd_disk_records: u64,
29 pub cd_records: u64,
30 pub cd_size: u64,
31 pub cd_pointer: u64,
32}
33
34#[derive(Serialize, Deserialize)]
35pub struct CDR {
36 pub version: u16,
37 pub version_needed: u16,
38 pub bit_flag: u16,
39 pub compression_method: u16,
40 pub dos_time: u16,
41 pub dos_date: u16,
42 pub crc32: u32,
43 pub compressed_size: u32,
44 pub size: u32,
45 pub name_len: u16,
46 pub extra_field_len: u16,
47 pub comment_len: u16,
48 pub disk: u16,
49 pub internal_attributes: u16,
50 pub external_attributes: u32,
51 pub header_pointer: u32,
52}
diff --git a/tests/usage.rs b/tests/usage.rs
new file mode 100644
index 0000000..64f7050
--- /dev/null
+++ b/tests/usage.rs
@@ -0,0 +1,19 @@
1use archivator::{Archive, Zip};
2use std::fs::File;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5fn time() -> f64 {
6 SystemTime::now()
7 .duration_since(UNIX_EPOCH)
8 .unwrap()
9 .as_secs_f64()
10}
11
12#[test]
13fn time_test() {
14 let file = File::open("tests/files/1M.zip").unwrap();
15
16 let start = time();
17 let archive = Archive::<Zip>::new(file).unwrap();
18 println!("{}", time() - start);
19}