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::{ENCRYPTED_FLAG, STRONG_ENCRYPTION_FLAG}};
6
7use super::{CompressionMethod, file_header::{FileHeaderExtraField, Zip64ProcessedData, Zip64OriginalData}};
8
9#[cfg(feature = "zipcrypto")]
10use crate::decrypt::zipcrypto::ZipCryptoDecryptor;
11
12pub const LFH_SIGNATURE: u32 = 0x04034b50;
13pub const LFH_CONSTANT_SIZE: usize = 26;
14
15#[derive(Debug, Clone)]
19pub struct LocalFileHeader {
20 pub version: u16,
21
22 pub flag: u16,
23
24 pub compression_method: Option<CompressionMethod>,
25
26 pub mod_time: u16,
27 pub mod_date: u16,
28
29 pub crc32: u32,
30
31 pub compressed_size: u64,
32 pub uncompressed_size: u64,
33
34 pub filename: String,
35
36 pub extra_fields: Vec<FileHeaderExtraField>,
37
38 pub zipcrypto_header: Option<[u8; 12]>,
39
40 pub header_size: usize
41}
42
43impl LocalFileHeader {
44 pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
47 let data = data.as_ref();
48 if data.len() < LFH_CONSTANT_SIZE {
49 return None;
50 }
51
52 let mut cursor = Cursor::new(data);
53
54 let version = cursor.read_u16::<LittleEndian>().unwrap();
55 let flag = cursor.read_u16::<LittleEndian>().unwrap();
56 let compression_method = cursor.read_u16::<LittleEndian>().unwrap();
57 let mod_time = cursor.read_u16::<LittleEndian>().unwrap();
58 let mod_date = cursor.read_u16::<LittleEndian>().unwrap();
59 let crc32 = cursor.read_u32::<LittleEndian>().unwrap();
60 let compressed_size = cursor.read_u32::<LittleEndian>().unwrap();
61 let uncompressed_size = cursor.read_u32::<LittleEndian>().unwrap();
62 let filename_length = cursor.read_u16::<LittleEndian>().unwrap();
63 let extra_fields_length = cursor.read_u16::<LittleEndian>().unwrap();
64
65 let filename_length = filename_length as usize;
66 let extra_fields_length = extra_fields_length as usize;
67 if data.len() < LFH_CONSTANT_SIZE + filename_length + extra_fields_length {
68 return None;
69 }
70
71 let compression_method = CompressionMethod::from_id(compression_method);
72
73 let filename_start = LFH_CONSTANT_SIZE;
74 let filename_end = filename_start + filename_length;
75 let filename = String::from_utf8_lossy(&data[filename_start..filename_end]).to_string();
76
77 let extra_fields_start = filename_end;
78 let extra_fields_end = extra_fields_start + extra_fields_length;
79 let Some(extra_fields) = FileHeaderExtraField::read_extra_fields(&data[extra_fields_start..extra_fields_end]) else {
80 return None;
81 };
82
83 let original_zip64_data = Zip64OriginalData {
84 uncompressed_size,
85 compressed_size,
86 ..Default::default()
87 };
88
89 let Some(Zip64ProcessedData {
90 uncompressed_size,
91 compressed_size,
92 ..
93 }) = original_zip64_data.process(&extra_fields) else {
94 return None;
95 };
96
97 let (zipcrypto_header, header_size) = if flag & ENCRYPTED_FLAG != 0 && flag & STRONG_ENCRYPTION_FLAG == 0 {
98 let zipcrypto_header_start = extra_fields_end;
99 let zipcrypto_header_end = zipcrypto_header_start + 12;
100 if zipcrypto_header_end > data.len() {
101 return None;
102 }
103
104 (Some(data[zipcrypto_header_start..zipcrypto_header_end].try_into().unwrap()), zipcrypto_header_end)
105 } else { (None, extra_fields_end) };
106
107 let compressed_size = if zipcrypto_header.is_some() { compressed_size - 12 } else { compressed_size };
108
109 Some(Self {
110 version,
111 flag,
112 compression_method,
113 mod_time,
114 mod_date,
115 crc32,
116 compressed_size,
117 uncompressed_size,
118 filename,
119 extra_fields,
120
121 zipcrypto_header,
122
123 header_size
124 })
125 }
126
127 pub fn is_directory(&self) -> bool {
128 self.filename.ends_with('/')
129 }
130
131 pub fn is_encrypted(&self) -> bool {
132 return self.flag & ENCRYPTED_FLAG != 0;
133 }
134
135 pub fn create_decryptor(&self, password: &[u8]) -> Result<Box<dyn Decryptor>, DecryptorCreationError> {
138 if !self.is_encrypted() {
139 return Err(DecryptorCreationError::NotEncrypted);
140 }
141
142 #[cfg(feature = "zipcrypto")]
144 if let Some(zipcrypto_header) = self.zipcrypto_header {
145 return Ok(Box::new(
146 ZipCryptoDecryptor::new(password, zipcrypto_header, self.crc32)?
147 ));
148 }
149
150 Err(DecryptorCreationError::Generic("unsupported encryption method".to_string()))
151 }
152}