Skip to main content

stream_unpack/zip/structures/
file_header.rs

1use std::io::Cursor;
2
3use byteorder::{ReadBytesExt, LittleEndian};
4
5pub const ENCRYPTED_FLAG: u16 = 0x0001;
6
7pub const COMPRESSION_AEX: u16 = 99;
8
9/// Contains raw ZIP file header extra field data
10#[derive(Debug, Clone)]
11pub struct FileHeaderExtraField {
12    pub id: u16,
13    pub data: Vec<u8>
14}
15
16impl FileHeaderExtraField {
17    /// Attempts to read a ZIP file header extra field. 
18    /// Returns None if there is not enough data
19    pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
20        let data = data.as_ref();
21        if data.len() < 4 {
22            return None;
23        }
24
25        let mut cursor = Cursor::new(data);
26
27        let id = cursor.read_u16::<LittleEndian>().unwrap();
28        let size = cursor.read_u16::<LittleEndian>().unwrap();
29
30        if data.len() < 4 + (size as usize) {
31            return None;
32        }
33
34        Some(Self {
35            id,
36            data: data[4..(4 + size as usize)].to_owned()
37        })
38    }
39
40    /// Attempts to read all ZIP file headers from the provided data.
41    /// Returns None if there is an error
42    pub fn read_extra_fields(data: impl AsRef<[u8]>) -> Option<Vec<FileHeaderExtraField>> {
43        let data = data.as_ref();
44
45        let mut extra_fileds = Vec::new();
46        let mut offset = 0;
47        while offset < data.len() {
48            let Some(field) = FileHeaderExtraField::from_bytes(&data[offset..]) else {
49                return None;
50            };
51
52            offset += field.size();
53            extra_fileds.push(field);
54        }
55
56        Some(extra_fileds)
57    }
58
59    /// The size of this field (together with the header)
60    pub fn size(&self) -> usize {
61        4 + self.data.len()
62    }
63}
64
65pub const ZIP64_EXTRA_FIELD_ID: u16 = 0x0001;
66pub const AEX_EXTRA_FIELD_ID:   u16 = 0x9901;
67
68#[derive(Debug, Default)]
69pub struct Zip64OriginalData {
70    pub uncompressed_size: u32,
71    pub compressed_size: u32,
72    pub local_header_offset: u32,
73    pub disk_number: u16
74}
75
76#[derive(Debug)]
77pub struct Zip64ProcessedData {
78    pub uncompressed_size: u64,
79    pub compressed_size: u64,
80    pub local_header_offset: u64,
81    pub disk_number: u32
82}
83
84impl Zip64OriginalData {
85    /// Processes a ZIP64 extra field if it exists in the input fields.
86    /// Returns None if processing fails
87    pub fn process(&self, fields: impl AsRef<[FileHeaderExtraField]>) -> Option<Zip64ProcessedData> {
88        let required_size = self.required_zip64_size();
89
90        if required_size == 0 {
91            return Some(self.as_processed());
92        }
93
94        let fields = fields.as_ref();
95        let Some(zip64) = fields.iter().find(|f| f.id == ZIP64_EXTRA_FIELD_ID) else {
96            return Some(self.as_processed());
97        };
98
99        if zip64.data.len() < required_size {
100            return None;
101        }
102
103        Some(self.read_all_if_needed(&zip64.data))
104    }
105
106    fn required_zip64_size(&self) -> usize {
107        (
108            if self.uncompressed_size   == u32::MAX { 8 } else { 0 } +
109            if self.compressed_size     == u32::MAX { 8 } else { 0 } + 
110            if self.local_header_offset == u32::MAX { 8 } else { 0 } +
111            if self.disk_number         == u16::MAX { 4 } else { 0 }
112        )
113    }
114
115    fn as_processed(&self) -> Zip64ProcessedData {
116        Zip64ProcessedData { 
117            compressed_size: self.compressed_size as u64, 
118            uncompressed_size: self.uncompressed_size as u64, 
119            local_header_offset: self.local_header_offset as u64, 
120            disk_number: self.disk_number as u32 
121        }
122    }
123
124    fn read_all_if_needed(&self, data: impl AsRef<[u8]>) -> Zip64ProcessedData {
125        let data = data.as_ref();
126        let mut cursor = Cursor::new(data);
127
128        let uncompressed_size = Self::read_u64_if_needed(&mut cursor, self.uncompressed_size);
129        let compressed_size = Self::read_u64_if_needed(&mut cursor, self.compressed_size);
130        let file_header_offset = Self::read_u64_if_needed(&mut cursor, self.local_header_offset);
131        let disk_number = Self::read_u32_if_needed(&mut cursor, self.disk_number);
132
133        Zip64ProcessedData { 
134            uncompressed_size, 
135            compressed_size,
136            local_header_offset: file_header_offset, 
137            disk_number
138        }
139    }
140
141    fn read_u64_if_needed(cursor: &mut Cursor<&[u8]>, value: u32) -> u64 {
142        if value != u32::MAX {
143            value as u64
144        } else {
145            cursor.read_u64::<LittleEndian>().unwrap()
146        }
147    }
148
149    fn read_u32_if_needed(cursor: &mut Cursor<&[u8]>, value: u16) -> u32 {
150        if value != u16::MAX {
151            value as u32
152        } else {
153            cursor.read_u32::<LittleEndian>().unwrap()
154        }
155    }
156}