1#[cfg(test)]
2mod test;
3
4use crate::io::Write;
5use std::{
6 fs::File,
7 io::{self, BufReader, BufWriter, Error, ErrorKind, Read, Result},
8 path::Path,
9 str::from_utf8,
10};
11
12pub struct ZipEntry {
13 pub name: String,
14 pub data: Vec<u8>,
15}
16
17pub struct Zip {
18 pub entries: Vec<ZipEntry>,
19}
20
21impl<P: AsRef<Path>> Write<P> for Zip {
22 type Error = Error;
23 fn write(&self, path: P) -> Result<()> {
24 self.write_to(&mut BufWriter::new(File::create(path)?))
25 }
26}
27
28impl Zip {
29 fn write_to<W: io::Write>(&self, file: &mut W) -> Result<()> {
30 let mut offset: u32 = 0;
31 let mut central = Vec::new();
32 for entry in &self.entries {
33 let crc = crc32(&entry.data);
34 let name = entry.name.as_bytes();
35 let size = entry.data.len() as u32;
36 file.write_all(b"PK\x03\x04")?;
37 file.write_all(&20u16.to_le_bytes())?;
38 file.write_all(&0u16.to_le_bytes())?;
39 file.write_all(&0u16.to_le_bytes())?;
40 file.write_all(&0u16.to_le_bytes())?;
41 file.write_all(&0x21u16.to_le_bytes())?;
42 file.write_all(&crc.to_le_bytes())?;
43 file.write_all(&size.to_le_bytes())?;
44 file.write_all(&size.to_le_bytes())?;
45 file.write_all(&(name.len() as u16).to_le_bytes())?;
46 file.write_all(&0u16.to_le_bytes())?;
47 file.write_all(name)?;
48 file.write_all(&entry.data)?;
49 central.extend_from_slice(b"PK\x01\x02");
50 central.extend_from_slice(&20u16.to_le_bytes());
51 central.extend_from_slice(&20u16.to_le_bytes());
52 central.extend_from_slice(&0u16.to_le_bytes());
53 central.extend_from_slice(&0u16.to_le_bytes());
54 central.extend_from_slice(&0u16.to_le_bytes());
55 central.extend_from_slice(&0x21u16.to_le_bytes());
56 central.extend_from_slice(&crc.to_le_bytes());
57 central.extend_from_slice(&size.to_le_bytes());
58 central.extend_from_slice(&size.to_le_bytes());
59 central.extend_from_slice(&(name.len() as u16).to_le_bytes());
60 central.extend_from_slice(&0u16.to_le_bytes());
61 central.extend_from_slice(&0u16.to_le_bytes());
62 central.extend_from_slice(&0u16.to_le_bytes());
63 central.extend_from_slice(&0u16.to_le_bytes());
64 central.extend_from_slice(&0u32.to_le_bytes());
65 central.extend_from_slice(&offset.to_le_bytes());
66 central.extend_from_slice(name);
67 offset += 30 + name.len() as u32 + size;
68 }
69 let central_size = central.len() as u32;
70 let central_offset = offset;
71 file.write_all(¢ral)?;
72 file.write_all(b"PK\x05\x06")?;
73 file.write_all(&0u16.to_le_bytes())?;
74 file.write_all(&0u16.to_le_bytes())?;
75 file.write_all(&(self.entries.len() as u16).to_le_bytes())?;
76 file.write_all(&(self.entries.len() as u16).to_le_bytes())?;
77 file.write_all(¢ral_size.to_le_bytes())?;
78 file.write_all(¢ral_offset.to_le_bytes())?;
79 file.write_all(&0u16.to_le_bytes())?;
80 file.flush()
81 }
82}
83
84impl Zip {
85 pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
86 let mut file = BufReader::new(File::open(path)?);
87 let mut bytes = Vec::new();
88 file.read_to_end(&mut bytes)?;
89 Self::read_from(&bytes)
90 }
91
92 fn read_from(bytes: &[u8]) -> Result<Self> {
93 let eocd_sig = [0x50, 0x4b, 0x05, 0x06];
94 let pos = bytes
95 .windows(4)
96 .rposition(|w| w == eocd_sig)
97 .ok_or_else(|| invalid("not a zip file (no end of central directory)".into()))?;
98 if pos + 22 > bytes.len() {
99 return Err(invalid("truncated end of central directory".into()));
100 }
101 let eocd = &bytes[pos..pos + 22];
102 let total_entries = u16::from_le_bytes([eocd[10], eocd[11]]) as usize;
103 let central_size = u32::from_le_bytes([eocd[12], eocd[13], eocd[14], eocd[15]]) as usize;
104 let central_offset = u32::from_le_bytes([eocd[16], eocd[17], eocd[18], eocd[19]]) as usize;
105 if central_offset > bytes.len() || central_offset + central_size > bytes.len() {
106 return Err(invalid("central directory out of bounds".into()));
107 }
108 let mut entries = Vec::with_capacity(total_entries);
109 let mut cursor = central_offset;
110 for _ in 0..total_entries {
111 if cursor + 46 > bytes.len() || &bytes[cursor..cursor + 4] != b"PK\x01\x02" {
112 return Err(invalid("malformed central directory record".into()));
113 }
114 let record = &bytes[cursor..cursor + 46];
115 let method = u16::from_le_bytes([record[10], record[11]]);
116 let size =
117 u32::from_le_bytes([record[24], record[25], record[26], record[27]]) as usize;
118 let name_len = u16::from_le_bytes([record[28], record[29]]) as usize;
119 let extra_len = u16::from_le_bytes([record[30], record[31]]) as usize;
120 let comment_len = u16::from_le_bytes([record[32], record[33]]) as usize;
121 let local_offset =
122 u32::from_le_bytes([record[42], record[43], record[44], record[45]]) as usize;
123 let name_start = cursor + 46;
124 let name_end = name_start + name_len;
125 if name_end > bytes.len() {
126 return Err(invalid("truncated file name".into()));
127 }
128 let name = from_utf8(&bytes[name_start..name_end])
129 .map_err(|_| invalid("non-UTF-8 entry name".into()))?
130 .to_string();
131 if method != 0 {
132 return Err(invalid(format!(
133 "unsupported compression method {method} for {name} (only stored entries are supported)"
134 )));
135 }
136 let data = read_local_entry(bytes, local_offset, size, &name)?;
137 entries.push(ZipEntry { name, data });
138 cursor = name_end + extra_len + comment_len;
139 }
140 Ok(Zip { entries })
141 }
142
143 pub fn entry(&self, name: &str) -> Option<&[u8]> {
144 self.entries
145 .iter()
146 .find(|entry| entry.name == name)
147 .map(|entry| entry.data.as_slice())
148 }
149}
150
151fn read_local_entry(bytes: &[u8], offset: usize, size: usize, name: &str) -> Result<Vec<u8>> {
152 if offset + 30 > bytes.len() || &bytes[offset..offset + 4] != b"PK\x03\x04" {
153 return Err(invalid(format!("malformed local file header for {name}")));
154 }
155 let name_len = u16::from_le_bytes([bytes[offset + 26], bytes[offset + 27]]) as usize;
156 let extra_len = u16::from_le_bytes([bytes[offset + 28], bytes[offset + 29]]) as usize;
157 let data_start = offset + 30 + name_len + extra_len;
158 let data_end = data_start + size;
159 if data_end > bytes.len() {
160 return Err(invalid(format!("truncated entry data for {name}")));
161 }
162 Ok(bytes[data_start..data_end].to_vec())
163}
164
165fn crc32(data: &[u8]) -> u32 {
166 let mut crc: u32 = 0xFFFF_FFFF;
167 for &byte in data {
168 crc ^= byte as u32;
169 for _ in 0..8 {
170 let mask = (crc & 1).wrapping_neg();
171 crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
172 }
173 }
174 !crc
175}
176
177fn invalid(message: String) -> Error {
178 Error::new(ErrorKind::InvalidData, message)
179}