1use std::collections::HashSet;
21use std::fs::File;
22use std::io::{self, BufWriter, Read, Write};
23use std::path::{Path, PathBuf};
24
25const DOS_TIME: u16 = 0;
27const DOS_DATE: u16 = (1 << 5) | 1;
29const FLAG_UTF8: u16 = 0x0800;
31const VERSION_NEEDED: u16 = 20;
33const VERSION_MADE_BY: u16 = (3 << 8) | 20;
35const FILE_ATTRIBUTES: u32 = 0o100_644 << 16;
36const DIR_ATTRIBUTES: u32 = (0o040_755 << 16) | 0x10;
37const MAX_ENTRIES: usize = 0xFFFF;
38const MAX_U32: u64 = 0xFFFF_FFFF;
39
40const SIG_LOCAL: u32 = 0x0403_4b50;
41const SIG_CENTRAL: u32 = 0x0201_4b50;
42const SIG_END: u32 = 0x0605_4b50;
43
44const CRC_TABLE: [u32; 256] = {
45 let mut table = [0u32; 256];
46 let mut n = 0;
47 while n < 256 {
48 let mut c = n as u32;
49 let mut k = 0;
50 while k < 8 {
51 c = if c & 1 != 0 {
52 0xEDB8_8320 ^ (c >> 1)
53 } else {
54 c >> 1
55 };
56 k += 1;
57 }
58 table[n] = c;
59 n += 1;
60 }
61 table
62};
63
64pub fn crc32(bytes: &[u8]) -> u32 {
67 let mut crc = 0xFFFF_FFFFu32;
68 for byte in bytes {
69 crc = CRC_TABLE[((crc ^ u32::from(*byte)) & 0xFF) as usize] ^ (crc >> 8);
70 }
71 !crc
72}
73
74fn invalid(message: impl Into<String>) -> io::Error {
75 io::Error::new(io::ErrorKind::InvalidInput, message.into())
76}
77
78fn check_name(name: &str, directory: bool) -> io::Result<()> {
80 let body = if directory {
81 name.strip_suffix('/').unwrap_or(name)
82 } else {
83 name
84 };
85 if body.is_empty() {
86 return Err(invalid("a ZIP entry name is empty"));
87 }
88 if name.len() > 0xFFFF {
89 return Err(invalid("a ZIP entry name is longer than 65535 bytes"));
90 }
91 if name.starts_with('/') || name.contains('\\') {
92 return Err(invalid(format!(
93 "ZIP entry name {name:?} is absolute or uses a backslash"
94 )));
95 }
96 if name.chars().any(|c| c.is_control()) {
97 return Err(invalid(format!(
98 "ZIP entry name {name:?} has a control character"
99 )));
100 }
101 let bytes = body.as_bytes();
102 if bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':' {
104 return Err(invalid(format!(
105 "ZIP entry name {name:?} has a drive prefix"
106 )));
107 }
108 for component in body.split('/') {
109 if component.is_empty() || component == "." || component == ".." {
110 return Err(invalid(format!(
111 "ZIP entry name {name:?} has an empty, `.` or `..` component"
112 )));
113 }
114 }
115 Ok(())
116}
117
118struct Entry {
119 name: String,
120 crc: u32,
121 size: u32,
122 offset: u32,
123 directory: bool,
124}
125
126pub struct StoredZipWriter<W: Write> {
130 out: W,
131 written: u64,
132 entries: Vec<Entry>,
133 seen: HashSet<String>,
134}
135
136impl<W: Write> StoredZipWriter<W> {
137 pub fn new(out: W) -> Self {
139 Self {
140 out,
141 written: 0,
142 entries: Vec::new(),
143 seen: HashSet::new(),
144 }
145 }
146
147 fn put(&mut self, bytes: &[u8]) -> io::Result<()> {
148 self.out.write_all(bytes)?;
149 self.written += bytes.len() as u64;
150 Ok(())
151 }
152
153 fn register(&mut self, name: &str, directory: bool) -> io::Result<String> {
154 check_name(name, directory)?;
155 if self.entries.len() >= MAX_ENTRIES {
156 return Err(invalid(
157 "a ZIP archive without ZIP64 holds at most 65535 entries",
158 ));
159 }
160 let stored = if directory && !name.ends_with('/') {
161 format!("{name}/")
162 } else {
163 name.to_string()
164 };
165 if stored.len() > 0xFFFF {
166 return Err(invalid("a ZIP entry name is longer than 65535 bytes"));
167 }
168 let twin = if directory {
170 stored.trim_end_matches('/').to_string()
171 } else {
172 format!("{stored}/")
173 };
174 if self.seen.contains(&stored) || self.seen.contains(&twin) {
175 return Err(invalid(format!("ZIP entry name {name:?} is already used")));
176 }
177 self.seen.insert(stored.clone());
178 Ok(stored)
179 }
180
181 pub fn add_file(&mut self, name: &str, data: &[u8]) -> io::Result<()> {
186 let size = u32::try_from(data.len())
187 .map_err(|_| invalid("a ZIP entry without ZIP64 is under 4 GiB"))?;
188 let stored = self.register(name, false)?;
189 self.write_local(stored, data, size, false)
190 }
191
192 pub fn add_dir(&mut self, name: &str) -> io::Result<()> {
194 let stored = self.register(name, true)?;
195 self.write_local(stored, &[], 0, true)
196 }
197
198 fn write_local(
199 &mut self,
200 name: String,
201 data: &[u8],
202 size: u32,
203 directory: bool,
204 ) -> io::Result<()> {
205 let offset = u32::try_from(self.written)
206 .map_err(|_| invalid("a ZIP entry starts past 4 GiB, which needs ZIP64"))?;
207 let crc = crc32(data);
208 let name_bytes = name.as_bytes();
209 let mut header = Vec::with_capacity(30 + name_bytes.len());
210 header.extend_from_slice(&SIG_LOCAL.to_le_bytes());
211 header.extend_from_slice(&VERSION_NEEDED.to_le_bytes());
212 header.extend_from_slice(&FLAG_UTF8.to_le_bytes());
213 header.extend_from_slice(&0u16.to_le_bytes()); header.extend_from_slice(&DOS_TIME.to_le_bytes());
215 header.extend_from_slice(&DOS_DATE.to_le_bytes());
216 header.extend_from_slice(&crc.to_le_bytes());
217 header.extend_from_slice(&size.to_le_bytes()); header.extend_from_slice(&size.to_le_bytes()); header.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
220 header.extend_from_slice(&0u16.to_le_bytes()); header.extend_from_slice(name_bytes);
222 self.put(&header)?;
223 self.put(data)?;
224 self.entries.push(Entry {
225 name,
226 crc,
227 size,
228 offset,
229 directory,
230 });
231 Ok(())
232 }
233
234 pub fn finish(mut self) -> io::Result<W> {
237 let directory_offset = u32::try_from(self.written).map_err(|_| {
238 invalid("the ZIP central directory starts past 4 GiB, which needs ZIP64")
239 })?;
240 let mut central = Vec::new();
241 for entry in &self.entries {
242 let name = entry.name.as_bytes();
243 central.extend_from_slice(&SIG_CENTRAL.to_le_bytes());
244 central.extend_from_slice(&VERSION_MADE_BY.to_le_bytes());
245 central.extend_from_slice(&VERSION_NEEDED.to_le_bytes());
246 central.extend_from_slice(&FLAG_UTF8.to_le_bytes());
247 central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&DOS_TIME.to_le_bytes());
249 central.extend_from_slice(&DOS_DATE.to_le_bytes());
250 central.extend_from_slice(&entry.crc.to_le_bytes());
251 central.extend_from_slice(&entry.size.to_le_bytes());
252 central.extend_from_slice(&entry.size.to_le_bytes());
253 central.extend_from_slice(&(name.len() as u16).to_le_bytes());
254 central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&0u16.to_le_bytes()); let attributes = if entry.directory {
259 DIR_ATTRIBUTES
260 } else {
261 FILE_ATTRIBUTES
262 };
263 central.extend_from_slice(&attributes.to_le_bytes());
264 central.extend_from_slice(&entry.offset.to_le_bytes());
265 central.extend_from_slice(name);
266 }
267 let directory_size = u32::try_from(central.len())
268 .ok()
269 .filter(|size| u64::from(*size) + u64::from(directory_offset) <= MAX_U32)
270 .ok_or_else(|| {
271 invalid("the ZIP central directory ends past 4 GiB, which needs ZIP64")
272 })?;
273 let count = self.entries.len() as u16;
274 let mut end = Vec::with_capacity(22);
275 end.extend_from_slice(&SIG_END.to_le_bytes());
276 end.extend_from_slice(&0u16.to_le_bytes()); end.extend_from_slice(&0u16.to_le_bytes()); end.extend_from_slice(&count.to_le_bytes());
279 end.extend_from_slice(&count.to_le_bytes());
280 end.extend_from_slice(&directory_size.to_le_bytes());
281 end.extend_from_slice(&directory_offset.to_le_bytes());
282 end.extend_from_slice(&0u16.to_le_bytes()); self.put(¢ral)?;
284 self.put(&end)?;
285 Ok(self.out)
286 }
287}
288
289pub fn stored_zip_bytes(entries: &[(&str, &[u8])]) -> io::Result<Vec<u8>> {
293 let mut writer = StoredZipWriter::new(Vec::new());
294 for (name, data) in entries {
295 if name.ends_with('/') {
296 if !data.is_empty() {
297 return Err(invalid(format!("directory entry {name:?} has content")));
298 }
299 writer.add_dir(name)?;
300 } else {
301 writer.add_file(name, data)?;
302 }
303 }
304 writer.finish()
305}
306
307const FILE_ZIP_MADE_BY: u16 = 0x031E;
312
313fn io_context(context: String, error: io::Error) -> io::Error {
314 io::Error::new(error.kind(), format!("{context}: {error}"))
315}
316
317pub fn crc_table() -> [u32; 256] {
319 CRC_TABLE
320}
321
322pub fn crc32_update(table: &[u32; 256], crc: u32, bytes: &[u8]) -> u32 {
324 let mut c = !crc;
325 for &b in bytes {
326 c = table[((c ^ b as u32) & 0xFF) as usize] ^ (c >> 8);
327 }
328 !c
329}
330
331fn file_crc_size(path: &Path) -> io::Result<(u32, u64)> {
332 let table = crc_table();
333 let mut f =
334 File::open(path).map_err(|e| io_context(format!("opening {}", path.display()), e))?;
335 let mut crc = 0u32;
336 let mut size = 0u64;
337 let mut buf = vec![0u8; 1 << 16];
338 loop {
339 let n = f
340 .read(&mut buf)
341 .map_err(|e| io_context(format!("reading {}", path.display()), e))?;
342 if n == 0 {
343 break;
344 }
345 crc = crc32_update(&table, crc, &buf[..n]);
346 size += n as u64;
347 }
348 Ok((crc, size))
349}
350
351pub fn write_zip(dest: &Path, entries: &[(String, PathBuf)]) -> io::Result<()> {
365 if entries.len() >= 0xFFFF {
366 return Err(invalid("too many files for a zip"));
367 }
368 for (name, _) in entries {
369 if name.len() > u16::MAX as usize {
370 return Err(invalid("archive name exceeds 65535 UTF-8 bytes"));
371 }
372 }
373 let io = |e| io_context(format!("writing {}", dest.display()), e);
374 let mut out = BufWriter::new(File::create(dest).map_err(io)?);
375 let mut offset: u64 = 0;
376 let mut central: Vec<u8> = Vec::new();
377 for (name, src) in entries {
378 let (crc, size) = file_crc_size(src)?;
379 if size >= 0xFFFF_FFFF || offset >= 0xFFFF_FFFF {
380 return Err(invalid(format!(
381 "{} is too large for a zip (ZIP64 is not written)",
382 src.display()
383 )));
384 }
385 let name_bytes = name.as_bytes();
387 checked_zip32(offset + 30 + name_bytes.len() as u64 + size)?;
388 checked_zip32(central.len() as u64 + 46 + name_bytes.len() as u64)?;
389 let mut local = Vec::with_capacity(30 + name_bytes.len());
390 local.extend_from_slice(&0x0403_4b50u32.to_le_bytes());
391 local.extend_from_slice(&VERSION_NEEDED.to_le_bytes());
392 local.extend_from_slice(&FLAG_UTF8.to_le_bytes());
393 local.extend_from_slice(&0u16.to_le_bytes()); local.extend_from_slice(&DOS_TIME.to_le_bytes());
395 local.extend_from_slice(&DOS_DATE.to_le_bytes());
396 local.extend_from_slice(&crc.to_le_bytes());
397 local.extend_from_slice(&(size as u32).to_le_bytes());
398 local.extend_from_slice(&(size as u32).to_le_bytes());
399 local.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
400 local.extend_from_slice(&0u16.to_le_bytes());
401 local.extend_from_slice(name_bytes);
402 out.write_all(&local).map_err(io)?;
403 let mut f =
404 File::open(src).map_err(|e| io_context(format!("opening {}", src.display()), e))?;
405 let (copied_crc, copied) = copy_counted_crc(&mut f, &mut out).map_err(io)?;
409 if copied != size || copied_crc != crc {
410 return Err(invalid(format!("{} changed while zipping", src.display())));
411 }
412
413 central.extend_from_slice(&0x0201_4b50u32.to_le_bytes());
414 central.extend_from_slice(&FILE_ZIP_MADE_BY.to_le_bytes());
415 central.extend_from_slice(&VERSION_NEEDED.to_le_bytes());
416 central.extend_from_slice(&FLAG_UTF8.to_le_bytes());
417 central.extend_from_slice(&0u16.to_le_bytes());
418 central.extend_from_slice(&DOS_TIME.to_le_bytes());
419 central.extend_from_slice(&DOS_DATE.to_le_bytes());
420 central.extend_from_slice(&crc.to_le_bytes());
421 central.extend_from_slice(&(size as u32).to_le_bytes());
422 central.extend_from_slice(&(size as u32).to_le_bytes());
423 central.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
424 central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&0u16.to_le_bytes()); central.extend_from_slice(&FILE_ATTRIBUTES.to_le_bytes());
429 central.extend_from_slice(&(offset as u32).to_le_bytes());
430 central.extend_from_slice(name_bytes);
431 offset += local.len() as u64 + size;
432 }
433 let cd_offset = offset;
434 checked_zip32(cd_offset)?;
435 checked_zip32(central.len() as u64)?;
436 out.write_all(¢ral).map_err(io)?;
437 let mut end = Vec::with_capacity(22);
438 end.extend_from_slice(&0x0605_4b50u32.to_le_bytes());
439 end.extend_from_slice(&0u16.to_le_bytes());
440 end.extend_from_slice(&0u16.to_le_bytes());
441 end.extend_from_slice(&(entries.len() as u16).to_le_bytes());
442 end.extend_from_slice(&(entries.len() as u16).to_le_bytes());
443 end.extend_from_slice(&(central.len() as u32).to_le_bytes());
444 end.extend_from_slice(&(cd_offset as u32).to_le_bytes());
445 end.extend_from_slice(&0u16.to_le_bytes());
446 out.write_all(&end).map_err(io)?;
447 out.flush().map_err(io)?;
448 Ok(())
449}
450
451fn copy_counted_crc<R: Read, W: Write>(src: &mut R, out: &mut W) -> io::Result<(u32, u64)> {
454 let table = crc_table();
455 let mut crc = 0u32;
456 let mut size = 0u64;
457 let mut buf = vec![0u8; 1 << 16];
458 loop {
459 let n = src.read(&mut buf)?;
460 if n == 0 {
461 break;
462 }
463 crc = crc32_update(&table, crc, &buf[..n]);
464 out.write_all(&buf[..n])?;
465 size += n as u64;
466 }
467 Ok((crc, size))
468}
469
470fn checked_zip32(value: u64) -> io::Result<()> {
471 if value >= u32::MAX as u64 {
472 Err(invalid("archive exceeds ZIP32 limits"))
473 } else {
474 Ok(())
475 }
476}
477
478pub fn read_zip(path: &Path) -> io::Result<Vec<(String, Vec<u8>)>> {
491 let data = std::fs::read(path)?;
492 parse_zip(&data)
493}
494
495pub fn read_zip_limited(path: &Path, max_bytes: u64) -> io::Result<Vec<(String, Vec<u8>)>> {
501 let file =
502 File::open(path).map_err(|e| io_context(format!("opening {}", path.display()), e))?;
503 let mut data = Vec::new();
504 file.take(max_bytes.saturating_add(1))
506 .read_to_end(&mut data)
507 .map_err(|e| io_context(format!("reading {}", path.display()), e))?;
508 if data.len() as u64 > max_bytes {
509 return Err(io::Error::new(
510 io::ErrorKind::InvalidData,
511 format!(
512 "{} is larger than the {max_bytes} byte limit for a zip",
513 path.display()
514 ),
515 ));
516 }
517 parse_zip(&data)
518}
519
520fn parse_zip(data: &[u8]) -> io::Result<Vec<(String, Vec<u8>)>> {
521 let bad = || io::Error::new(io::ErrorKind::InvalidData, "invalid STORED zip");
522 let u16at = |o: usize| -> io::Result<usize> {
523 let bytes: [u8; 2] = data
524 .get(o..o.checked_add(2).ok_or_else(bad)?)
525 .ok_or_else(bad)?
526 .try_into()
527 .map_err(|_| bad())?;
528 Ok(u16::from_le_bytes(bytes) as usize)
529 };
530 let u32at = |o: usize| -> io::Result<u32> {
531 let bytes: [u8; 4] = data
532 .get(o..o.checked_add(4).ok_or_else(bad)?)
533 .ok_or_else(bad)?
534 .try_into()
535 .map_err(|_| bad())?;
536 Ok(u32::from_le_bytes(bytes))
537 };
538 let eocd = data.len().checked_sub(22).ok_or_else(bad)?;
539 if u32at(eocd)? != 0x0605_4b50
540 || u16at(eocd + 4)? != 0
541 || u16at(eocd + 6)? != 0
542 || u16at(eocd + 20)? != 0
543 {
544 return Err(bad());
545 }
546 let count = u16at(eocd + 10)?;
547 if count >= 0xffff || u16at(eocd + 8)? != count {
548 return Err(bad());
549 }
550 let central_size = u32at(eocd + 12)? as usize;
551 let central_start = u32at(eocd + 16)? as usize;
552 if central_start.checked_add(central_size) != Some(eocd) {
553 return Err(bad());
554 }
555 let table = crc_table();
556 let mut p = central_start;
557 let mut local_end = 0;
558 let mut out = Vec::new();
559 for _ in 0..count {
560 let header_end = p.checked_add(46).filter(|&n| n <= eocd).ok_or_else(bad)?;
561 if u32at(p)? != 0x0201_4b50
562 || u16at(p + 8)? != 0x0800
563 || u16at(p + 10)? != 0
564 || u16at(p + 30)? != 0
565 || u16at(p + 32)? != 0
566 || u16at(p + 34)? != 0
567 {
568 return Err(bad());
569 }
570 let crc = u32at(p + 16)?;
571 let size = u32at(p + 24)? as usize;
572 if u32at(p + 20)? as usize != size {
573 return Err(bad());
574 }
575 let name_len = u16at(p + 28)?;
576 let next = header_end
577 .checked_add(name_len)
578 .filter(|&n| n <= eocd)
579 .ok_or_else(bad)?;
580 let name_bytes = &data[header_end..next];
581 let name = std::str::from_utf8(name_bytes)
582 .map_err(|_| bad())?
583 .to_owned();
584 let local = u32at(p + 42)? as usize;
585 let start = local
586 .checked_add(30)
587 .and_then(|n| n.checked_add(name_len))
588 .filter(|&n| n <= central_start)
589 .ok_or_else(bad)?;
590 let end = start
591 .checked_add(size)
592 .filter(|&n| n <= central_start)
593 .ok_or_else(bad)?;
594 if local != local_end
595 || u32at(local)? != 0x0403_4b50
596 || u16at(local + 6)? != 0x0800
597 || u16at(local + 8)? != 0
598 || u32at(local + 14)? != crc
599 || u32at(local + 18)? as usize != size
600 || u32at(local + 22)? as usize != size
601 || u16at(local + 26)? != name_len
602 || u16at(local + 28)? != 0
603 || &data[local + 30..start] != name_bytes
604 {
605 return Err(bad());
606 }
607 let bytes = &data[start..end];
608 if crc32_update(&table, 0, bytes) != crc {
609 return Err(bad());
610 }
611 out.push((name, bytes.to_vec()));
612 local_end = end;
613 p = next;
614 }
615 if p != eocd || local_end != central_start {
616 return Err(bad());
617 }
618 Ok(out)
619}