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