stream_unpack/zip/structures/
local_file_header.rs1use std::io::Cursor;
2
3use byteorder::{ReadBytesExt, LittleEndian};
4
5use super::{CompressionMethod, file_header::{FileHeaderExtraField, Zip64ProcessedData, Zip64OriginalData}};
6
7pub const LFH_SIGNATURE: u32 = 0x04034b50;
8pub const LFH_CONSTANT_SIZE: usize = 26;
9
10#[derive(Debug, Clone)]
14pub struct LocalFileHeader {
15 pub version: u16,
16
17 pub flag: u16,
18
19 pub compression_method: Option<CompressionMethod>,
20
21 pub mod_time: u16,
22 pub mod_date: u16,
23
24 pub crc32: u32,
25
26 pub compressed_size: u64,
27 pub uncompressed_size: u64,
28
29 pub filename: String,
30
31 pub extra_fields: Vec<FileHeaderExtraField>,
32
33 pub header_size: usize
34}
35
36impl LocalFileHeader {
37 pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
40 let data = data.as_ref();
41 if data.len() < LFH_CONSTANT_SIZE {
42 return None;
43 }
44
45 let mut cursor = Cursor::new(data);
46
47 let version = cursor.read_u16::<LittleEndian>().unwrap();
48 let flag = cursor.read_u16::<LittleEndian>().unwrap();
49 let compression_method = cursor.read_u16::<LittleEndian>().unwrap();
50 let mod_time = cursor.read_u16::<LittleEndian>().unwrap();
51 let mod_date = cursor.read_u16::<LittleEndian>().unwrap();
52 let crc32 = cursor.read_u32::<LittleEndian>().unwrap();
53 let compressed_size = cursor.read_u32::<LittleEndian>().unwrap();
54 let uncompressed_size = cursor.read_u32::<LittleEndian>().unwrap();
55 let filename_length = cursor.read_u16::<LittleEndian>().unwrap();
56 let extra_fields_length = cursor.read_u16::<LittleEndian>().unwrap();
57
58 let filename_length = filename_length as usize;
59 let extra_fields_length = extra_fields_length as usize;
60 if data.len() < LFH_CONSTANT_SIZE + filename_length + extra_fields_length {
61 return None;
62 }
63
64 let compression_method = CompressionMethod::from_id(compression_method);
65
66 let filename_start = LFH_CONSTANT_SIZE;
67 let filename_end = filename_start + filename_length;
68 let filename = String::from_utf8_lossy(&data[filename_start..filename_end]).to_string();
69
70 let extra_fields_start = filename_end;
71 let extra_fields_end = extra_fields_start + extra_fields_length;
72 let Some(extra_fields) = FileHeaderExtraField::read_extra_fields(&data[extra_fields_start..extra_fields_end]) else {
73 return None;
74 };
75
76 let original_zip64_data = Zip64OriginalData {
77 uncompressed_size,
78 compressed_size,
79 ..Default::default()
80 };
81
82 let Some(Zip64ProcessedData {
83 uncompressed_size,
84 compressed_size,
85 ..
86 }) = original_zip64_data.process(&extra_fields) else {
87 return None;
88 };
89
90 Some(Self {
91 version,
92 flag,
93 compression_method,
94 mod_time,
95 mod_date,
96 crc32,
97 compressed_size,
98 uncompressed_size,
99 filename,
100 extra_fields,
101
102 header_size: extra_fields_end
103 })
104 }
105
106 pub fn is_directory(&self) -> bool {
107 self.filename.ends_with('/')
108 }
109}