stream_unpack/zip/structures/
local_file_header.rs

1use 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/// Represents the result of reading a ZIP local file header (LFH)
11/// 
12/// The layout of this object does not follow the original ZIP LFH structure
13#[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    /// Attempts to read a local file header from the provided
38    /// byte buffer. Returns None if there isn't enought data
39    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}