1use crate::central_dir::EntryLocation;
10
11const LOCAL_HEADER_SIG: u32 = 0x04034b50;
12
13#[derive(Debug)]
14pub struct ZipError(pub &'static str);
15
16impl std::fmt::Display for ZipError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "ZIP error: {}", self.0)
19 }
20}
21
22impl std::error::Error for ZipError {}
23
24pub struct ZipEntry {
25 pub name: String,
26 pub data: Vec<u8>,
27}
28
29pub fn decompress_entry(data: &[u8], loc: &EntryLocation) -> Result<Vec<u8>, ZipError> {
35 let hdr = loc.local_header_offset as usize;
36
37 let sig = data.get(hdr..hdr + 4)
38 .and_then(|b| b.try_into().ok())
39 .map(u32::from_le_bytes)
40 .ok_or(ZipError("local file header out of bounds"))?;
41 if sig != LOCAL_HEADER_SIG {
42 return Err(ZipError("invalid local file header signature"));
43 }
44
45 let name_len = data.get(hdr + 26..hdr + 28)
47 .and_then(|b| b.try_into().ok())
48 .map(u16::from_le_bytes)
49 .ok_or(ZipError("local header truncated at name len"))? as usize;
50
51 let extra_len = data.get(hdr + 28..hdr + 30)
52 .and_then(|b| b.try_into().ok())
53 .map(u16::from_le_bytes)
54 .ok_or(ZipError("local header truncated at extra len"))? as usize;
55
56 let data_start = hdr + 30 + name_len + extra_len;
57 let data_end = data_start + loc.compressed_size as usize;
58
59 let compressed = data.get(data_start..data_end)
60 .ok_or(ZipError("compressed data out of bounds"))?;
61
62 decompress_entry_raw(compressed, loc.compression_method, loc.uncompressed_size as usize)
63}
64
65pub fn decompress_entry_raw(compressed: &[u8], method: u16, expected_size: usize) -> Result<Vec<u8>, ZipError> {
72 match method {
73 0 => Ok(compressed.to_vec()),
74 8 => inflate_raw(compressed, expected_size),
75 _ => Err(ZipError("unsupported compression method")),
76 }
77}
78
79#[cfg(not(feature = "zlib-ng"))]
80fn inflate_raw(compressed: &[u8], expected_size: usize) -> Result<Vec<u8>, ZipError> {
81 let size = if expected_size > 0 {
82 expected_size
83 } else {
84 (compressed.len() * 4).max(4096)
85 };
86 linflate::inflate_to_vec(compressed, size)
87 .map_err(|_| ZipError("DEFLATE decompression failed"))
88}
89
90#[cfg(feature = "zlib-ng")]
91fn inflate_raw(compressed: &[u8], expected_size: usize) -> Result<Vec<u8>, ZipError> {
92 use std::io::Read;
93 let mut decoder = flate2::read::DeflateDecoder::new(compressed);
94 let mut out = if expected_size > 0 {
95 Vec::with_capacity(expected_size)
96 } else {
97 Vec::new()
98 };
99 decoder
100 .read_to_end(&mut out)
101 .map_err(|_| ZipError("DEFLATE decompression failed (zlib-ng)"))?;
102 Ok(out)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::central_dir::EntryLocation;
109
110 #[rustfmt::skip]
112 static HELLO_JAR: &[u8] = &[
113 0x50, 0x4B, 0x03, 0x04,
115 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
116 0x00, 0x00, 0x00, 0x00,
117 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, b'h', b'e', b'l', b'l', b'o', b'.', b't', b'x', b't',
123 b'H', b'e', b'l', b'l', b'o', b',', b' ',
124 b'W', b'o', b'r', b'l', b'd', b'!', b'\n',
125 ];
126
127 #[test]
128 fn store_entry() {
129 let loc = EntryLocation {
130 name: "hello.txt".into(),
131 local_header_offset: 0,
132 compressed_size: 14,
133 uncompressed_size: 14,
134 compression_method: 0,
135 crc32: 0,
136 is_directory: false,
137 };
138 let out = decompress_entry(HELLO_JAR, &loc).unwrap();
139 assert_eq!(out, b"Hello, World!\n");
140 }
141}