1use std::collections::BTreeMap;
16use std::io::{self, Write};
17use std::ops::Range;
18
19use flate2::write::DeflateDecoder;
20use flate2::Crc;
21use opys_core::Manifest;
22
23use crate::{
24 blob_entry, not_a_bundle, parse_head, parse_manifest, BundleError, HEAD_ENTRY, LOCAL_HEADER,
25 LOCAL_HEADER_LEN, MANIFEST_ENTRY,
26};
27
28const END: [u8; 4] = *b"PK\x05\x06";
29const END_LEN: usize = 22;
30const END64_LOCATOR: [u8; 4] = *b"PK\x06\x07";
31const END64_LOCATOR_LEN: usize = 20;
32const END64: [u8; 4] = *b"PK\x06\x06";
33const END64_LEN: usize = 56;
34const CENTRAL: [u8; 4] = *b"PK\x01\x02";
35const CENTRAL_LEN: usize = 46;
36const LONGEST_COMMENT: usize = u16::MAX as usize;
39const ZIP64_EXTRA: u16 = 0x0001;
40const ENCRYPTED: u16 = 1;
41
42const STORED: u16 = 0;
43const DEFLATED: u16 = 8;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Entry {
48 range: Range<u64>,
49 method: u16,
50 compressed: u64,
51 size: u64,
52 crc: u32,
53}
54
55impl Entry {
56 pub fn range(&self) -> Range<u64> {
58 self.range.clone()
59 }
60
61 pub fn size(&self) -> u64 {
63 self.size
64 }
65
66 pub fn decoder<W: Write>(&self, out: W) -> EntryDecoder<W> {
69 EntryDecoder {
70 entry: self.clone(),
71 state: State::Header(Vec::new()),
72 out: Some(out),
73 }
74 }
75
76 fn decode(&self, raw: &[u8]) -> Result<Vec<u8>, BundleError> {
78 let mut decoder = self.decoder(Vec::with_capacity(self.size as usize));
79 decoder.write_all(raw)?;
80 decoder.finish()
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct Directory {
87 len: u64,
88 entries: BTreeMap<String, Entry>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum Back {
94 Directory(Directory),
95 Needs(u64),
98}
99
100fn short(bytes: &[u8], at: usize) -> u16 {
101 u16::from_le_bytes([bytes[at], bytes[at + 1]])
102}
103
104fn long(bytes: &[u8], at: usize) -> u32 {
105 u32::from_le_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
106}
107
108fn wide(bytes: &[u8], at: usize) -> u64 {
109 let mut word = [0; 8];
110 word.copy_from_slice(&bytes[at..at + 8]);
111 u64::from_le_bytes(word)
112}
113
114fn end_record(tail: &[u8]) -> Option<usize> {
118 let last = tail.len().checked_sub(END_LEN)?;
119 (0..=last).rev().find(|&at| {
120 tail[at..at + 4] == END && usize::from(short(tail, at + 20)) == tail.len() - at - END_LEN
121 })
122}
123
124pub fn directory_from_back(tail: &[u8], file_len: u64) -> Result<Back, BundleError> {
127 let given = tail.len() as u64;
128 if given > file_len {
129 return Err(not_a_bundle("it is shorter than its own tail"));
130 }
131 let at = |offset: u64| offset.checked_sub(file_len - given).map(|at| at as usize);
133 let short_of = |offset: u64| Ok(Back::Needs(file_len - offset));
134
135 let Some(end) = end_record(tail) else {
136 let reach = file_len.min((END_LEN + LONGEST_COMMENT) as u64);
137 return if given < reach {
138 Ok(Back::Needs(reach))
139 } else {
140 Err(not_a_bundle("it has no zip directory"))
141 };
142 };
143 let (mut count, mut size, mut offset) = (
144 u64::from(short(tail, end + 10)),
145 u64::from(long(tail, end + 12)),
146 u64::from(long(tail, end + 16)),
147 );
148 if count == u64::from(u16::MAX) || size == u64::from(u32::MAX) || offset == u64::from(u32::MAX)
152 {
153 let locator = end
154 .checked_sub(END64_LOCATOR_LEN)
155 .filter(|&locator| tail[locator..locator + 4] == END64_LOCATOR)
156 .ok_or_else(|| not_a_bundle("its zip64 directory has no locator"))?;
157 let record = wide(tail, locator + 8);
158 let Some(record) = at(record) else {
159 return short_of(record);
160 };
161 if tail
162 .get(record..record + END64_LEN)
163 .map_or(true, |r| r[..4] != END64)
164 {
165 return Err(not_a_bundle(
166 "its zip64 directory is not where its locator says",
167 ));
168 }
169 (count, size, offset) = (
170 wide(tail, record + 32),
171 wide(tail, record + 40),
172 wide(tail, record + 48),
173 );
174 }
175 let Some(start) = at(offset) else {
176 return short_of(offset);
177 };
178 let central = usize::try_from(size)
179 .ok()
180 .and_then(|size| tail.get(start..start.checked_add(size)?))
181 .ok_or_else(|| not_a_bundle("its directory runs past its end"))?;
182
183 let mut found: Vec<(String, Entry)> = Vec::new();
184 let mut at_entry = 0;
185 while at_entry < central.len() {
186 let fixed = central
187 .get(at_entry..at_entry + CENTRAL_LEN)
188 .filter(|fixed| fixed[..4] == CENTRAL)
189 .ok_or_else(|| not_a_bundle("its directory holds something that is not an entry"))?;
190 if short(fixed, 8) & ENCRYPTED != 0 {
191 return Err(not_a_bundle("it has an encrypted entry"));
192 }
193 let (name_len, extra_len, comment_len) = (
194 usize::from(short(fixed, 28)),
195 usize::from(short(fixed, 30)),
196 usize::from(short(fixed, 32)),
197 );
198 let name_at = at_entry + CENTRAL_LEN;
199 let extra_at = name_at + name_len;
200 let next = extra_at + extra_len + comment_len;
201 let (name, extra) = central
202 .get(name_at..extra_at)
203 .zip(central.get(extra_at..extra_at + extra_len))
204 .filter(|_| next <= central.len())
205 .ok_or_else(|| not_a_bundle("an entry of its directory runs past it"))?;
206 let name = std::str::from_utf8(name)
207 .map_err(|_| not_a_bundle("an entry's name is not UTF-8"))?
208 .to_owned();
209
210 let mut size = u64::from(long(fixed, 24));
211 let mut compressed = u64::from(long(fixed, 20));
212 let mut local = u64::from(long(fixed, 42));
213 let mut wide_values = zip64_values(extra)?.into_iter();
214 for value in [&mut size, &mut compressed, &mut local] {
215 if *value == u64::from(u32::MAX) {
216 *value = wide_values
217 .next()
218 .ok_or_else(|| not_a_bundle("an entry lacks its zip64 sizes"))?;
219 }
220 }
221 found.push((
222 name,
223 Entry {
224 range: local..local,
225 method: short(fixed, 10),
226 compressed,
227 size,
228 crc: long(fixed, 16),
229 },
230 ));
231 at_entry = next;
232 }
233 if found.len() as u64 != count {
234 return Err(not_a_bundle(
235 "its directory does not hold the entries it counts",
236 ));
237 }
238
239 found.sort_by_key(|(_, entry)| entry.range.start);
242 let ends: Vec<u64> = found
243 .iter()
244 .skip(1)
245 .map(|(_, entry)| entry.range.start)
246 .chain([offset])
247 .collect();
248 let mut entries = BTreeMap::new();
249 for ((name, mut entry), end) in found.into_iter().zip(ends) {
250 if end < entry.range.start + LOCAL_HEADER_LEN as u64 + entry.compressed {
251 return Err(not_a_bundle("its entries overlap"));
252 }
253 entry.range.end = end;
254 if entries.insert(name, entry).is_some() {
255 return Err(not_a_bundle("it names one entry twice"));
256 }
257 }
258 Ok(Back::Directory(Directory {
259 len: file_len,
260 entries,
261 }))
262}
263
264fn zip64_values(mut extra: &[u8]) -> Result<Vec<u64>, BundleError> {
267 while extra.len() >= 4 {
268 let (id, len) = (short(extra, 0), usize::from(short(extra, 2)));
269 let body = extra
270 .get(4..4 + len)
271 .ok_or_else(|| not_a_bundle("an entry's extra field runs past it"))?;
272 if id == ZIP64_EXTRA {
273 return Ok(body.chunks_exact(8).map(|word| wide(word, 0)).collect());
274 }
275 extra = &extra[4 + len..];
276 }
277 Ok(Vec::new())
278}
279
280impl Directory {
281 pub fn file_len(&self) -> u64 {
283 self.len
284 }
285
286 pub fn front(&self) -> Result<Range<u64>, BundleError> {
289 let head = self.entry(HEAD_ENTRY)?;
290 let manifest = self.entry(MANIFEST_ENTRY)?;
291 Ok(head.range.start.min(manifest.range.start)..head.range.end.max(manifest.range.end))
292 }
293
294 pub fn open(&self, front: &[u8]) -> Result<Manifest, BundleError> {
299 let base = self.front()?.start;
300 let entry_bytes = |name: &'static str| {
301 let entry = self.entry(name)?;
302 let raw = front
303 .get((entry.range.start - base) as usize..(entry.range.end - base) as usize)
304 .ok_or_else(|| not_a_bundle("its front is shorter than its directory says"))?;
305 entry.decode(raw)
306 };
307 parse_head(&entry_bytes(HEAD_ENTRY)?)?;
308 let manifest = parse_manifest(&entry_bytes(MANIFEST_ENTRY)?)?;
309 for id in manifest.blob_ids() {
310 self.blob(id)?;
311 }
312 Ok(manifest)
313 }
314
315 pub fn blob(&self, id: &str) -> Result<&Entry, BundleError> {
317 self.entries
318 .get(&blob_entry(id))
319 .ok_or_else(|| BundleError::MissingBlob(id.to_owned()))
320 }
321
322 fn entry(&self, name: &'static str) -> Result<&Entry, BundleError> {
323 self.entries
324 .get(name)
325 .ok_or(BundleError::MissingEntry(name))
326 }
327}
328
329enum State<W: Write> {
331 Header(Vec<u8>),
333 Data { left: u64, sink: Sink<W> },
335 Done(Checked<W>),
337}
338
339enum Sink<W: Write> {
340 Stored(Checked<W>),
341 Deflated(DeflateDecoder<Checked<W>>),
342}
343
344struct Checked<W> {
346 inner: W,
347 crc: Crc,
348}
349
350impl<W: Write> Write for Checked<W> {
351 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
352 let written = self.inner.write(buf)?;
353 self.crc.update(&buf[..written]);
354 Ok(written)
355 }
356
357 fn flush(&mut self) -> io::Result<()> {
358 self.inner.flush()
359 }
360}
361
362pub struct EntryDecoder<W: Write> {
364 entry: Entry,
365 state: State<W>,
366 out: Option<W>,
367}
368
369fn invalid(why: &'static str) -> io::Error {
370 io::Error::new(io::ErrorKind::InvalidData, why)
371}
372
373impl<W: Write> EntryDecoder<W> {
374 fn begin(&mut self, header: &[u8]) -> io::Result<Option<(usize, State<W>)>> {
377 if header.len() < LOCAL_HEADER_LEN {
378 return Ok(None);
379 }
380 if header[..4] != LOCAL_HEADER {
381 return Err(invalid("an entry does not begin with its local header"));
382 }
383 let start =
384 LOCAL_HEADER_LEN + usize::from(short(header, 26)) + usize::from(short(header, 28));
385 if header.len() < start {
386 return Ok(None);
387 }
388 let out = Checked {
389 inner: self.out.take().expect("the sink is made once"),
390 crc: Crc::new(),
391 };
392 let sink = match self.entry.method {
393 STORED => Sink::Stored(out),
394 DEFLATED => Sink::Deflated(DeflateDecoder::new(out)),
395 _ => return Err(invalid("an entry is compressed in a way a bundle is not")),
396 };
397 let data = State::Data {
398 left: self.entry.compressed,
399 sink,
400 };
401 Ok(Some((start, data)))
402 }
403
404 pub fn get_mut(&mut self) -> &mut W {
406 match &mut self.state {
407 State::Header(_) => self.out.as_mut().expect("the sink is made once"),
408 State::Data {
409 sink: Sink::Stored(out),
410 ..
411 }
412 | State::Done(out) => &mut out.inner,
413 State::Data {
414 sink: Sink::Deflated(out),
415 ..
416 } => &mut out.get_mut().inner,
417 }
418 }
419
420 pub fn finish(self) -> Result<W, BundleError> {
422 let checked = match self.state {
423 State::Done(checked) => checked,
424 State::Data { left: 0, sink } => finish_sink(sink)?,
425 _ => return Err(not_a_bundle("an entry ends before its data does")),
426 };
427 if checked.crc.amount() as u64 != self.entry.size & u64::from(u32::MAX)
428 || checked.crc.sum() != self.entry.crc
429 {
430 return Err(not_a_bundle(
431 "an entry does not decode to what its directory says",
432 ));
433 }
434 Ok(checked.inner)
435 }
436}
437
438fn finish_sink<W: Write>(sink: Sink<W>) -> io::Result<Checked<W>> {
439 match sink {
440 Sink::Stored(checked) => Ok(checked),
441 Sink::Deflated(decoder) => decoder.finish(),
442 }
443}
444
445impl<W: Write> Write for EntryDecoder<W> {
446 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
447 let mut rest = buf;
448 while !rest.is_empty() {
449 self.state = match std::mem::replace(&mut self.state, State::Header(Vec::new())) {
450 State::Header(mut header) => {
451 let before = header.len();
452 header.extend_from_slice(rest);
453 match self.begin(&header)? {
454 Some((start, data)) => {
456 rest = &rest[start - before..];
457 data
458 }
459 None => {
460 rest = &[];
461 State::Header(header)
462 }
463 }
464 }
465 State::Data { left, mut sink } => {
466 let take = rest.len().min(usize::try_from(left).unwrap_or(usize::MAX));
467 match &mut sink {
468 Sink::Stored(out) => out.write_all(&rest[..take])?,
469 Sink::Deflated(out) => out.write_all(&rest[..take])?,
470 }
471 rest = &rest[take..];
472 match left - take as u64 {
473 0 => State::Done(finish_sink(sink)?),
474 left => State::Data { left, sink },
475 }
476 }
477 done @ State::Done(_) => {
479 rest = &[];
480 done
481 }
482 };
483 }
484 Ok(buf.len())
485 }
486
487 fn flush(&mut self) -> io::Result<()> {
488 match &mut self.state {
489 State::Data {
490 sink: Sink::Stored(out),
491 ..
492 }
493 | State::Done(out) => out.flush(),
494 State::Data {
495 sink: Sink::Deflated(out),
496 ..
497 } => out.flush(),
498 State::Header(_) => Ok(()),
499 }
500 }
501}