stream_unpack/zip/structures/
file_header.rs

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