diff options
| author | Igor Tolmachev <me@igorek.dev> | 2024-06-15 03:30:50 +0900 |
|---|---|---|
| committer | Igor Tolmachev <me@igorek.dev> | 2024-06-23 15:34:34 +0900 |
| commit | f8c3c93824645a807d28b760855b4676ea479720 (patch) | |
| tree | 1f91838c2abcb3b0683a061f892b8e2835be4fa1 /src/zip/driver.rs | |
| parent | bd77f62e99a5300dfa52aef3a7040414b28ebfd6 (diff) | |
| download | archivator-f8c3c93824645a807d28b760855b4676ea479720.tar.gz archivator-f8c3c93824645a807d28b760855b4676ea479720.zip | |
Add simple zip reader
Diffstat (limited to 'src/zip/driver.rs')
| -rw-r--r-- | src/zip/driver.rs | 143 |
1 files changed, 143 insertions, 0 deletions
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 @@ | |||
| 1 | use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; | ||
| 2 | use crate::zip::error::{ZipError, ZipResult}; | ||
| 3 | use crate::zip::structs::{EOCDR64Locator, CDR, EOCDR, EOCDR64}; | ||
| 4 | use crate::zip::ZipFile; | ||
| 5 | use std::collections::HashMap as Map; | ||
| 6 | use std::fs::File; | ||
| 7 | use std::io::{Read, Seek, SeekFrom, Write}; | ||
| 8 | |||
| 9 | pub struct Zip<IO = File> { | ||
| 10 | io: IO, | ||
| 11 | |||
| 12 | files: Map<String, ZipFile>, | ||
| 13 | comment: String, | ||
| 14 | } | ||
| 15 | |||
| 16 | impl<IO> Driver for Zip<IO> { | ||
| 17 | type Error = ZipError; | ||
| 18 | |||
| 19 | type IO = IO; | ||
| 20 | type File = ZipFile; | ||
| 21 | } | ||
| 22 | |||
| 23 | impl<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 | |||
| 139 | impl<IO: Read + Seek> Zip<IO> {} | ||
| 140 | |||
| 141 | impl<IO: Read + Write + Seek> ArchiveWrite for Zip<IO> {} | ||
| 142 | |||
| 143 | impl<IO: Read + Write> Zip<IO> {} | ||
