stream_unpack/zip/structures/
cd_location.rs

1use std::io::Cursor;
2
3use byteorder::{LittleEndian, ReadBytesExt};
4
5pub const CDLD_MAX_SIZE: usize = EOCD32_MAX_SIZE + 4 + EOCD64_LOCATOR_CONSTANT_SIZE;
6
7#[derive(Debug, Clone)]
8pub struct CentralDirectoryLocationData {
9    pub cd_disk_number: u32,
10    pub cd_size: u64,
11    pub cd_offset: u64,
12
13    #[cfg(feature = "zip-comments")]
14    pub comment: String
15}
16
17impl CentralDirectoryLocationData {
18    pub fn from_eocd32(eocd32: EndOfCentralDirectory32) -> Self {
19        Self {
20            cd_disk_number: eocd32.cd_disk_number as u32,
21            cd_size: eocd32.cd_size as u64,
22            cd_offset: eocd32.cd_offset as u64,
23
24            #[cfg(feature = "zip-comments")]
25            comment: eocd32.comment
26        }
27    }
28
29    pub fn from_eocd64(eocd32: EndOfCentralDirectory32, eocd64: EndOfCentralDirectory64) -> Self {
30        let mut data = Self::from_eocd32(eocd32);
31
32        if data.cd_disk_number == u16::MAX as u32 {
33            data.cd_disk_number = eocd64.cd_disk_number;
34        }
35
36        if data.cd_size == u32::MAX as u64 {
37            data.cd_size = eocd64.cd_size;
38        }
39
40        if data.cd_offset == u32::MAX as u64 {
41            data.cd_offset = eocd64.cd_offset;
42        }
43
44        data
45    }
46}
47
48pub const EOCD32_SIGNATURE: u32 = 0x06054b50;
49pub const EOCD32_CONSTANT_SIZE: usize = 18;
50pub const EOCD32_MAX_SIZE: usize = EOCD32_CONSTANT_SIZE + 4 + (u16::MAX as usize);
51
52#[derive(Debug, Clone)]
53pub struct EndOfCentralDirectory32 {
54    pub disk_number: u16,
55    pub cd_disk_number: u16,
56
57    pub cd_entry_count: u16,
58    pub cd_entry_count_total: u16,
59
60    pub cd_size: u32,
61    pub cd_offset: u32,
62
63    #[cfg(feature = "zip-comments")]
64    pub comment: String,
65
66    pub eocd32_size: usize
67}
68
69impl EndOfCentralDirectory32 {
70    pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
71        let data = data.as_ref();
72        if data.len() < EOCD32_CONSTANT_SIZE {
73            return None;
74        }
75
76        let mut cursor = Cursor::new(data);
77        
78        let disk_number = cursor.read_u16::<LittleEndian>().unwrap();
79        let cd_disk_number = cursor.read_u16::<LittleEndian>().unwrap();
80        let cd_entry_count = cursor.read_u16::<LittleEndian>().unwrap();
81        let cd_entry_count_total = cursor.read_u16::<LittleEndian>().unwrap();
82        let cd_size = cursor.read_u32::<LittleEndian>().unwrap();
83        let cd_offset = cursor.read_u32::<LittleEndian>().unwrap();
84        let comment_length = cursor.read_u16::<LittleEndian>().unwrap();
85
86        if data.len() < EOCD32_CONSTANT_SIZE + comment_length as usize {
87            return None;
88        }
89
90        let comment_start = EOCD32_CONSTANT_SIZE;
91        let comment_end = comment_start + comment_length as usize;
92
93        Some(Self {
94            disk_number,
95            cd_disk_number,
96            cd_entry_count,
97            cd_entry_count_total,
98            cd_size,
99            cd_offset,
100
101            #[cfg(feature = "zip-comments")]
102            comment: String::from_utf8_lossy(&data[comment_start..comment_end]).to_string(),
103
104            eocd32_size: comment_end
105        })
106    }
107
108    /// Tries to find an end of central directory structure at the end of given data.
109    /// This searches at most 2^16 bytes
110    /// 
111    /// Returns the offset of this structure within the data if it is found
112    pub fn find_offset(data: impl AsRef<[u8]>) -> Option<usize> {
113        let data = data.as_ref();
114        if data.len() < EOCD32_CONSTANT_SIZE {
115            return None;
116        }
117
118        let first_offset = data.len() - std::cmp::min(data.len(), EOCD32_MAX_SIZE);
119
120        for offset in (first_offset..(data.len() - EOCD32_CONSTANT_SIZE)).rev() {
121            let signature = u32::from_le_bytes(data[offset..(offset + 4)].try_into().unwrap());
122            if signature == EOCD32_SIGNATURE {
123                return Some(offset);
124            }
125        }
126
127        None
128    }
129
130    pub fn requires_zip64(&self) -> bool {
131        self.disk_number == u16::MAX ||
132        self.cd_disk_number == u16::MAX ||
133        self.cd_entry_count == u16::MAX ||
134        self.cd_entry_count_total == u16::MAX ||
135        self.cd_size == u32::MAX ||
136        self.cd_offset == u32::MAX
137    }
138}
139
140pub const EOCD64_LOCATOR_SIGNATURE: u32 = 0x07064b50;
141pub const EOCD64_LOCATOR_CONSTANT_SIZE: usize = 16;
142
143#[derive(Debug, Clone)]
144pub struct EndOfCentralDirectory64Locator {
145    pub eocd64_disk_number: u32,
146    pub eocd64_offset: u64,
147
148    pub disk_count: u32
149}
150
151impl EndOfCentralDirectory64Locator {
152    pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
153        let data = data.as_ref();
154        if data.len() < EOCD64_LOCATOR_CONSTANT_SIZE {
155            return None;
156        }
157
158        let mut cursor = Cursor::new(data);
159
160        let eocd64_disk_number = cursor.read_u32::<LittleEndian>().unwrap();
161        let eocd64_offset = cursor.read_u64::<LittleEndian>().unwrap();
162        let disk_count = cursor.read_u32::<LittleEndian>().unwrap();
163
164        Some(Self {
165            eocd64_disk_number,
166            eocd64_offset,
167            disk_count
168        })
169    }
170}
171
172pub const EOCD64_SIGNATURE: u32 = 0x06064b50;
173pub const EOCD64_CONSTANT_SIZE: usize = 52;
174
175#[derive(Debug, Clone)]
176pub struct EndOfCentralDirectory64 {
177    pub eocd64_size: u64,
178
179    pub version_made_by: u16,
180    pub version_needed: u16,
181
182    pub disk_number: u32,
183    pub cd_disk_number: u32,
184
185    pub cd_entry_count: u64,
186    pub cd_entry_count_total: u64,
187
188    pub cd_size: u64,
189    pub cd_offset: u64
190}
191
192impl EndOfCentralDirectory64 {
193    pub fn from_bytes(data: impl AsRef<[u8]>) -> Option<Self> {
194        let data = data.as_ref();
195        if data.len() < EOCD64_CONSTANT_SIZE {
196            return None;
197        }
198
199        let mut cursor = Cursor::new(data);
200
201        let eocd64_size = cursor.read_u64::<LittleEndian>().unwrap();
202        let version_made_by = cursor.read_u16::<LittleEndian>().unwrap();
203        let version_needed = cursor.read_u16::<LittleEndian>().unwrap();
204        let disk_number = cursor.read_u32::<LittleEndian>().unwrap();
205        let cd_disk_number = cursor.read_u32::<LittleEndian>().unwrap();
206        let cd_entry_count = cursor.read_u64::<LittleEndian>().unwrap();
207        let cd_entry_count_total = cursor.read_u64::<LittleEndian>().unwrap();
208        let cd_size = cursor.read_u64::<LittleEndian>().unwrap();
209        let cd_offset = cursor.read_u64::<LittleEndian>().unwrap();
210
211        // TODO: extensible data field
212
213        Some(Self {
214            eocd64_size,
215            version_made_by,
216            version_needed,
217            disk_number,
218            cd_disk_number,
219            cd_entry_count,
220            cd_entry_count_total,
221            cd_size,
222            cd_offset
223        })
224    }
225}