stream_unpack/zip/structures/
local_file_header.rs1use std::io::Cursor;
2
3use byteorder::{ReadBytesExt, LittleEndian};
4
5use crate::{decrypt::{Decryptor, DecryptorCreationError}, zip::structures::file_header::{AEX_EXTRA_FIELD_ID, COMPRESSION_AEX, ENCRYPTED_FLAG}};
6
7use super::{CompressionMethod, file_header::{FileHeaderExtraField, Zip64ProcessedData, Zip64OriginalData}};
8
9#[cfg(feature = "zipcrypto")]
10use crate::decrypt::zipcrypto::ZipCryptoDecryptor;
11
12#[cfg(feature = "ae-x")]
13use crate::decrypt::aex::*;
14
15pub const LFH_SIGNATURE: u32 = 0x04034b50;
16pub const LFH_CONSTANT_SIZE: usize = 26;
17
18#[derive(Debug, Clone)]
22pub struct LocalFileHeader {
23 pub version: u16,
24
25 pub flag: u16,
26
27 pub compression_method: Option<CompressionMethod>,
28
29 pub mod_time: u16,
30 pub mod_date: u16,
31
32 pub crc32: u32,
33
34 pub compressed_size: u64,
35 pub uncompressed_size: u64,
36
37 pub filename: String,
38
39 pub extra_fields: Vec<FileHeaderExtraField>,
40
41 pub encryption: EncryptionData,
42
43 pub header_size: usize
44}
45
46impl LocalFileHeader {
47 pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
50 let data = data.as_ref();
51 if data.len() < LFH_CONSTANT_SIZE {
52 return None;
53 }
54
55 let mut cursor = Cursor::new(data);
56
57 let version = cursor.read_u16::<LittleEndian>().unwrap();
58 let flag = cursor.read_u16::<LittleEndian>().unwrap();
59 let compression_method = cursor.read_u16::<LittleEndian>().unwrap();
60 let mod_time = cursor.read_u16::<LittleEndian>().unwrap();
61 let mod_date = cursor.read_u16::<LittleEndian>().unwrap();
62 let crc32 = cursor.read_u32::<LittleEndian>().unwrap();
63 let compressed_size = cursor.read_u32::<LittleEndian>().unwrap();
64 let uncompressed_size = cursor.read_u32::<LittleEndian>().unwrap();
65 let filename_length = cursor.read_u16::<LittleEndian>().unwrap();
66 let extra_fields_length = cursor.read_u16::<LittleEndian>().unwrap();
67
68 let filename_length = filename_length as usize;
69 let extra_fields_length = extra_fields_length as usize;
70 if data.len() < LFH_CONSTANT_SIZE + filename_length + extra_fields_length {
71 return None;
72 }
73
74 let filename_start = LFH_CONSTANT_SIZE;
75 let filename_end = filename_start + filename_length;
76 let filename = String::from_utf8_lossy(&data[filename_start..filename_end]).to_string();
77
78 let extra_fields_start = filename_end;
79 let extra_fields_end = extra_fields_start + extra_fields_length;
80 let extra_fields = FileHeaderExtraField::read_extra_fields(&data[extra_fields_start..extra_fields_end])?;
81
82 let original_zip64_data = Zip64OriginalData {
83 uncompressed_size,
84 compressed_size,
85 ..Default::default()
86 };
87
88 let Zip64ProcessedData {
89 uncompressed_size,
90 compressed_size,
91 ..
92 } = original_zip64_data.process(&extra_fields)?;
93
94 let mut compressed_size = compressed_size;
95 let mut compression_method = compression_method;
96 let mut header_size = extra_fields_end;
97 let mut encryption = EncryptionData::None;
98 if flag & ENCRYPTED_FLAG != 0 {
99 if compression_method == COMPRESSION_AEX {
100 let aex_ef = extra_fields.iter()
101 .find(|f| f.id == AEX_EXTRA_FIELD_ID)?;
102 if aex_ef.size() < 7 {
103 return None;
104 }
105
106 let strength = aex_ef.data[4];
107
108 let salt_length = match strength {
109 0x01 => 8, 0x02 => 12, 0x03 => 16,
110 _ => { return None; }
111 };
112
113 let salt_start = header_size;
114 let salt_end = salt_start + salt_length;
115 if salt_end > data.len() {
116 return None;
117 }
118
119 let salt = &data[salt_start..salt_end];
120 let aex_variant = match strength {
121 0x01 => AExVariableData::AES128(salt.try_into().unwrap()),
122 0x02 => AExVariableData::AES192(salt.try_into().unwrap()),
123 0x03 => AExVariableData::AES256(salt.try_into().unwrap()),
124 _ => { return None; }
125 };
126
127 let pvv_start = salt_end;
128 let pvv_end = pvv_start + 2;
129 if pvv_end > data.len() {
130 return None;
131 }
132
133 let pvv = u16::from_le_bytes([data[pvv_start], data[pvv_start + 1]]);
134
135 compressed_size -= (salt_length + 2 + 10) as u64;
137 compression_method = u16::from_le_bytes([aex_ef.data[5], aex_ef.data[6]]);
138 header_size = pvv_end;
139 encryption = EncryptionData::AEx(AExInitData { variable: aex_variant, pvv })
140 } else {
141 let zipcrypto_header_start = header_size;
142 let zipcrypto_header_end = zipcrypto_header_start + 12;
143 if zipcrypto_header_end > data.len() {
144 return None;
145 }
146
147 compressed_size -= 12;
148 header_size = zipcrypto_header_end;
149 encryption = EncryptionData::ZipCrypto(
150 data[zipcrypto_header_start..zipcrypto_header_end].try_into().unwrap()
151 );
152 }
153 }
154
155 Some(Self {
156 version,
157 flag,
158 compression_method: CompressionMethod::from_id(compression_method),
159 mod_time,
160 mod_date,
161 crc32,
162 compressed_size,
163 uncompressed_size,
164 filename,
165 extra_fields,
166
167 encryption,
168
169 header_size
170 })
171 }
172
173 pub fn is_directory(&self) -> bool {
174 self.filename.ends_with('/')
175 }
176
177 pub fn is_encrypted(&self) -> bool {
178 return !matches!(self.encryption, EncryptionData::None);
179 }
180
181 pub fn create_decryptor(&self, password: &[u8]) -> Result<Box<dyn Decryptor>, DecryptorCreationError> {
184 match &self.encryption {
185 EncryptionData::None => Err(DecryptorCreationError::NotEncrypted),
186
187 EncryptionData::ZipCrypto(zipcrypto_header) => {
188 #[cfg(feature = "zipcrypto")] {
189 Ok(Box::new(ZipCryptoDecryptor::new(password, *zipcrypto_header, self.crc32)?))
190 }
191 #[cfg(not(feature = "zipcrypto"))] {
192 let _ = password;
193 let _ = zipcrypto_header;
194 Err(DecryptorCreationError::NoFeature("zipcrypto".to_string()))
195 }
196 }
197
198 EncryptionData::AEx(data) => {
199 #[cfg(feature = "ae-x")] {
200 data.create_decryptor(password)
201 }
202 #[cfg(not(feature = "ae-x"))] {
203 let _ = password;
204 let _ = data;
205 Err(DecryptorCreationError::NoFeature("ae-x".to_string()))
206 }
207 }
208 }
209 }
210}
211
212#[derive(Debug, Clone)]
213pub enum EncryptionData {
214 None,
215 ZipCrypto([u8; 12]),
216 AEx(AExInitData)
217}
218
219#[derive(Debug, Clone)]
220pub enum AExVariableData {
221 AES128([u8; 8]),
222 AES192([u8; 12]),
223 AES256([u8; 16])
224}
225
226#[derive(Debug, Clone)]
227pub struct AExInitData {
228 pub variable: AExVariableData,
229 pub pvv: u16
230}
231
232impl AExInitData {
233 #[cfg(feature = "ae-x")]
234 fn create_decryptor(&self, password: &[u8]) -> Result<Box<dyn Decryptor>, DecryptorCreationError> {
235 match &self.variable {
236 AExVariableData::AES128(salt) => Ok(Box::new(AEx128Decryptor::new(password, salt, self.pvv)?)),
237 AExVariableData::AES192(salt) => Ok(Box::new(AEx192Decryptor::new(password, salt, self.pvv)?)),
238 AExVariableData::AES256(salt) => Ok(Box::new(AEx256Decryptor::new(password, salt, self.pvv)?))
239 }
240 }
241}