1use std::borrow::Cow;
2
3use ownable::{traits as ownable_traits, IntoOwned, ToOwned};
4use tracing::trace;
5use winnow::{
6 binary::{le_u16, le_u32, le_u64, length_take},
7 seq,
8 token::literal,
9 PResult, Parser, Partial,
10};
11
12use crate::error::{Error, FormatError};
13
14#[derive(Debug, ToOwned, IntoOwned, Clone)]
16pub struct EndOfCentralDirectoryRecord<'a> {
17 pub disk_nbr: u16,
19
20 pub dir_disk_nbr: u16,
22
23 pub dir_records_this_disk: u16,
25
26 pub directory_records: u16,
28
29 pub directory_size: u32,
31
32 pub directory_offset: u32,
34
35 pub comment: Cow<'a, [u8]>,
37}
38
39impl<'a> EndOfCentralDirectoryRecord<'a> {
40 const MIN_LENGTH: usize = 20;
42 const SIGNATURE: &'static str = "PK\x05\x06";
43
44 pub fn find_in_block(b: &'a [u8]) -> Option<Located<Self>> {
46 for i in (0..(b.len().saturating_sub(Self::MIN_LENGTH + 1))).rev() {
47 let mut input = Partial::new(&b[i..]);
48 if let Ok(directory) = Self::parser.parse_next(&mut input) {
49 return Some(Located {
50 offset: i as u64,
51 inner: directory,
52 });
53 }
54 }
55 None
56 }
57
58 pub fn parser(i: &mut Partial<&'a [u8]>) -> PResult<Self> {
60 let _ = literal(Self::SIGNATURE).parse_next(i)?;
61 seq! {Self {
62 disk_nbr: le_u16,
63 dir_disk_nbr: le_u16,
64 dir_records_this_disk: le_u16,
65 directory_records: le_u16,
66 directory_size: le_u32,
67 directory_offset: le_u32,
68 comment: length_take(le_u16).map(Cow::Borrowed),
69 }}
70 .parse_next(i)
71 }
72}
73
74#[derive(Debug)]
76pub struct EndOfCentralDirectory64Locator {
77 pub dir_disk_number: u32,
79 pub directory_offset: u64,
81 pub total_disks: u32,
83}
84
85impl EndOfCentralDirectory64Locator {
86 pub const LENGTH: usize = 20;
88 const SIGNATURE: &'static str = "PK\x06\x07";
89
90 pub fn parser(i: &mut Partial<&'_ [u8]>) -> PResult<Self> {
92 _ = literal(Self::SIGNATURE).parse_next(i)?;
93 seq! {Self {
94 dir_disk_number: le_u32,
95 directory_offset: le_u64,
96 total_disks: le_u32,
97 }}
98 .parse_next(i)
99 }
100}
101
102#[derive(Debug, Clone, ToOwned, IntoOwned)]
104pub struct EndOfCentralDirectory64Record {
105 pub record_size: u64,
107
108 pub creator_version: u16,
110
111 pub reader_version: u16,
113
114 pub disk_nbr: u32,
116
117 pub dir_disk_nbr: u32,
119
120 pub dir_records_this_disk: u64,
122
123 pub directory_records: u64,
125
126 pub directory_size: u64,
128
129 pub directory_offset: u64,
132}
133
134impl EndOfCentralDirectory64Record {
135 const SIGNATURE: &'static str = "PK\x06\x06";
136
137 pub fn parser(i: &mut Partial<&'_ [u8]>) -> PResult<Self> {
139 _ = literal(Self::SIGNATURE).parse_next(i)?;
140 seq! {Self {
141 record_size: le_u64,
142 creator_version: le_u16,
143 reader_version: le_u16,
144 disk_nbr: le_u32,
145 dir_disk_nbr: le_u32,
146 dir_records_this_disk: le_u64,
147 directory_records: le_u64,
148 directory_size: le_u64,
149 directory_offset: le_u64,
150 }}
151 .parse_next(i)
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct Located<T> {
158 pub offset: u64,
160
161 pub inner: T,
163}
164
165impl<T> ownable_traits::ToOwned for Located<T>
166where
167 T: ownable_traits::ToOwned,
168{
169 type Owned = Located<T::Owned>;
170
171 fn to_owned(&self) -> Self::Owned {
172 Located {
173 offset: self.offset,
174 inner: self.inner.to_owned(),
175 }
176 }
177}
178
179impl<T> ownable_traits::IntoOwned for Located<T>
180where
181 T: ownable_traits::IntoOwned,
182{
183 type Owned = Located<T::Owned>;
184
185 fn into_owned(self) -> Self::Owned {
186 Located {
187 offset: self.offset,
188 inner: self.inner.into_owned(),
189 }
190 }
191}
192
193#[derive(ToOwned, IntoOwned)]
195pub struct EndOfCentralDirectory<'a> {
196 pub dir: Located<EndOfCentralDirectoryRecord<'a>>,
198
199 pub dir64: Option<Located<EndOfCentralDirectory64Record>>,
201
202 pub global_offset: i64,
205}
206
207impl<'a> EndOfCentralDirectory<'a> {
208 pub(crate) fn new(
209 size: u64,
210 dir: Located<EndOfCentralDirectoryRecord<'a>>,
211 dir64: Option<Located<EndOfCentralDirectory64Record>>,
212 ) -> Result<Self, Error> {
213 let mut res = Self {
214 dir,
215 dir64,
216 global_offset: 0,
217 };
218
219 let computed_directory_offset = res
249 .located_directory_offset()
250 .checked_sub(res.directory_size())
251 .ok_or(FormatError::DirectoryOffsetPointsOutsideFile)?;
252
253 if (0..size).contains(&computed_directory_offset) {
255 if computed_directory_offset != res.directory_offset() {
257 res.global_offset =
259 computed_directory_offset as i64 - res.directory_offset() as i64;
260 res.set_directory_offset(computed_directory_offset);
261 }
262 }
263
264 trace!(
266 "directory offset = {}, valid range = 0..{}",
267 res.directory_offset(),
268 size
269 );
270 if !(0..size).contains(&res.directory_offset()) {
271 return Err(FormatError::DirectoryOffsetPointsOutsideFile.into());
272 }
273
274 Ok(res)
275 }
276
277 #[inline]
278 pub(crate) fn located_directory_offset(&self) -> u64 {
279 match self.dir64.as_ref() {
280 Some(d64) => d64.offset,
281 None => self.dir.offset,
282 }
283 }
284
285 #[inline]
286 pub(crate) fn directory_offset(&self) -> u64 {
287 match self.dir64.as_ref() {
288 Some(d64) => d64.inner.directory_offset,
289 None => self.dir.inner.directory_offset as u64,
290 }
291 }
292
293 #[inline]
294 pub(crate) fn directory_size(&self) -> u64 {
295 match self.dir64.as_ref() {
296 Some(d64) => d64.inner.directory_size,
297 None => self.dir.inner.directory_size as u64,
298 }
299 }
300
301 #[inline]
302 pub(crate) fn set_directory_offset(&mut self, offset: u64) {
303 match self.dir64.as_mut() {
304 Some(d64) => d64.inner.directory_offset = offset,
305 None => self.dir.inner.directory_offset = offset as u32,
306 };
307 }
308
309 #[inline]
310 pub(crate) fn directory_records(&self) -> u64 {
311 match self.dir64.as_ref() {
312 Some(d64) => d64.inner.directory_records,
313 None => self.dir.inner.directory_records as u64,
314 }
315 }
316
317 #[inline]
318 pub(crate) fn comment(&self) -> &[u8] {
319 &self.dir.inner.comment
320 }
321}