1use std::collections::VecDeque;
14use std::io::{Read, Seek, SeekFrom};
15
16use crate::central_dir;
17use crate::entry::{self, ZipEntry, ZipError};
18
19const BATCH_SIZE: usize = 64;
21
22const LOCAL_HEADER_SIG: u32 = 0x04034b50;
23
24pub struct StreamingZipRead<R: Read + Seek> {
39 source: R,
40 locations: Vec<crate::central_dir::EntryLocation>,
42 next_entry: usize,
43 buf: VecDeque<ZipEntry>,
44 error: bool,
45}
46
47impl<R: Read + Seek> StreamingZipRead<R> {
48 pub fn new(mut source: R) -> Result<Self, ZipError> {
53 let all_locs = central_dir::read_central_directory_from(&mut source)?;
54 let mut locations: Vec<_> = all_locs.into_iter().filter(|l| !l.is_directory).collect();
55 locations.sort_by_key(|l| l.local_header_offset);
56 Ok(Self {
57 source,
58 locations,
59 next_entry: 0,
60 buf: VecDeque::new(),
61 error: false,
62 })
63 }
64
65 pub fn with_filter(mut source: R, needle: &str) -> Result<Self, ZipError> {
70 let all_locs = central_dir::read_central_directory_from(&mut source)?;
71 let mut locations: Vec<_> = all_locs.into_iter()
72 .filter(|l| !l.is_directory && l.name.contains(needle))
73 .collect();
74 locations.sort_by_key(|l| l.local_header_offset);
75 Ok(Self {
76 source,
77 locations,
78 next_entry: 0,
79 buf: VecDeque::new(),
80 error: false,
81 })
82 }
83
84 pub fn with_filter_set(mut source: R, names: &[&str]) -> Result<Self, ZipError> {
88 use std::collections::HashSet;
89 let name_set: HashSet<&str> = names.iter().copied().collect();
90 let all_locs = central_dir::read_central_directory_from(&mut source)?;
91 let mut locations: Vec<_> = all_locs.into_iter()
92 .filter(|l| !l.is_directory && name_set.contains(l.name.as_str()))
93 .collect();
94 locations.sort_by_key(|l| l.local_header_offset);
95 Ok(Self {
96 source,
97 locations,
98 next_entry: 0,
99 buf: VecDeque::new(),
100 error: false,
101 })
102 }
103
104 pub fn with_filter_suffixes(mut source: R, suffixes: &[&str]) -> Result<Self, ZipError> {
108 let all_locs = central_dir::read_central_directory_from(&mut source)?;
109 let mut locations: Vec<_> = all_locs.into_iter()
110 .filter(|l| !l.is_directory && suffixes.iter().any(|s| l.name.ends_with(s)))
111 .collect();
112 locations.sort_by_key(|l| l.local_header_offset);
113 Ok(Self {
114 source,
115 locations,
116 next_entry: 0,
117 buf: VecDeque::new(),
118 error: false,
119 })
120 }
121
122 fn fill_batch(&mut self) -> Result<(), ZipError> {
123 let remaining = self.locations.len() - self.next_entry;
124 if remaining == 0 {
125 return Ok(());
126 }
127
128 let batch_end = self.next_entry + remaining.min(BATCH_SIZE);
129 let batch = &self.locations[self.next_entry..batch_end];
130
131 let mut raw: Vec<(String, Vec<u8>, u16, usize)> = Vec::with_capacity(batch.len());
133 for loc in batch {
134 self.source.seek(SeekFrom::Start(loc.local_header_offset))
135 .map_err(|_| ZipError("seek to local header failed"))?;
136
137 let mut hdr = [0u8; 30];
138 self.source.read_exact(&mut hdr)
139 .map_err(|_| ZipError("read local header failed"))?;
140
141 if u32::from_le_bytes(hdr[0..4].try_into().unwrap()) != LOCAL_HEADER_SIG {
142 return Err(ZipError("invalid local file header signature"));
143 }
144
145 let skip = u16::from_le_bytes([hdr[26], hdr[27]]) as i64
147 + u16::from_le_bytes([hdr[28], hdr[29]]) as i64;
148 self.source.seek(SeekFrom::Current(skip))
149 .map_err(|_| ZipError("seek past local header name/extra failed"))?;
150
151 let len = loc.compressed_size as usize;
154 let mut compressed = Vec::with_capacity(len);
155 #[allow(clippy::uninit_vec)]
158 unsafe {
159 compressed.set_len(len);
160 }
161 self.source.read_exact(&mut compressed)
162 .map_err(|_| ZipError("read compressed data failed"))?;
163
164 raw.push((loc.name.clone(), compressed, loc.compression_method, loc.uncompressed_size as usize));
165 }
166
167 let results: Vec<Result<ZipEntry, ZipError>> =
175 gatling::gatling_forkjoin::gatling_for_each_balanced(
176 raw.len(),
177 0,
178 1,
179 |i| raw[i].3 as u64,
180 |i| {
181 let (name, compressed, method, expected_size) = &raw[i];
182 let data = entry::decompress_entry_raw(compressed, *method, *expected_size)?;
183 Ok(ZipEntry { name: name.clone(), data })
184 },
185 );
186
187 for r in results {
189 self.buf.push_back(r?);
190 }
191 self.next_entry = batch_end;
192 Ok(())
193 }
194}
195
196impl<R: Read + Seek> Iterator for StreamingZipRead<R> {
197 type Item = Result<ZipEntry, ZipError>;
198
199 fn next(&mut self) -> Option<Self::Item> {
200 if self.error {
201 return None;
202 }
203 if let Some(entry) = self.buf.pop_front() {
204 return Some(Ok(entry));
205 }
206 if self.next_entry >= self.locations.len() {
207 return None;
208 }
209 match self.fill_batch() {
210 Err(e) => {
211 self.error = true;
212 Some(Err(e))
213 }
214 Ok(()) => self.buf.pop_front().map(Ok),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use std::io::{Cursor, Write};
223
224 fn make_jar(items: &[(&str, &[u8])]) -> Vec<u8> {
225 use zip::write::SimpleFileOptions;
226 let mut buf = Vec::new();
227 let mut zw = zip::ZipWriter::new(Cursor::new(&mut buf));
228 let opts = SimpleFileOptions::default()
229 .compression_method(zip::CompressionMethod::Deflated);
230 for (name, data) in items {
231 zw.start_file(*name, opts).unwrap();
232 zw.write_all(data).unwrap();
233 }
234 zw.finish().unwrap();
235 buf
236 }
237
238 #[test]
239 fn streaming_single() {
240 let jar = make_jar(&[("Hello.class", b"cafebabe")]);
241 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
242 .unwrap()
243 .collect::<Result<Vec<_>, _>>()
244 .unwrap();
245 assert_eq!(entries.len(), 1);
246 assert_eq!(entries[0].name, "Hello.class");
247 assert_eq!(entries[0].data, b"cafebabe");
248 }
249
250 #[test]
251 fn streaming_multi() {
252 let jar = make_jar(&[("a.txt", b"aaa"), ("b.txt", b"bbb"), ("c.txt", b"ccc")]);
253 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
254 .unwrap()
255 .collect::<Result<Vec<_>, _>>()
256 .unwrap();
257 assert_eq!(entries.len(), 3);
258 }
259
260 #[test]
261 fn streaming_skips_directories() {
262 let jar = make_jar(&[("META-INF/", b""), ("META-INF/MANIFEST.MF", b"Manifest-Version: 1.0\n")]);
263 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
264 .unwrap()
265 .collect::<Result<Vec<_>, _>>()
266 .unwrap();
267 assert_eq!(entries.len(), 1);
268 assert_eq!(entries[0].name, "META-INF/MANIFEST.MF");
269 }
270
271 #[test]
272 fn streaming_many_batches() {
273 let items: Vec<(String, Vec<u8>)> = (0..200)
274 .map(|i| (format!("Class{i}.class"), format!("data{i}").into_bytes()))
275 .collect();
276 let item_refs: Vec<(&str, &[u8])> = items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
277 let jar = make_jar(&item_refs);
278
279 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
280 .unwrap()
281 .collect::<Result<Vec<_>, _>>()
282 .unwrap();
283 assert_eq!(entries.len(), 200);
284 }
285
286 #[test]
287 fn streaming_matches_parallel() {
288 let items: Vec<(String, Vec<u8>)> = (0..100)
289 .map(|i| (format!("{i}.txt"), format!("content {i}").into_bytes()))
290 .collect();
291 let item_refs: Vec<(&str, &[u8])> = items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
292 let jar = make_jar(&item_refs);
293
294 let par = crate::parallel::decompress_parallel(&jar).unwrap();
295 let stream: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
296 .unwrap()
297 .collect::<Result<Vec<_>, _>>()
298 .unwrap();
299
300 let mut par_sorted = par;
302 par_sorted.sort_by(|a, b| a.name.cmp(&b.name));
303 let mut stream_sorted = stream;
304 stream_sorted.sort_by(|a, b| a.name.cmp(&b.name));
305
306 assert_eq!(par_sorted.len(), stream_sorted.len());
307 for (p, s) in par_sorted.iter().zip(stream_sorted.iter()) {
308 assert_eq!(p.name, s.name);
309 assert_eq!(p.data, s.data);
310 }
311 }
312
313 #[test]
318 fn streaming_skewed_roundtrip_byte_identical() {
319 let big: Vec<u8> = (0..300_000u32)
320 .map(|i| (i.wrapping_mul(2_654_435_761) >> 13) as u8)
321 .collect();
322 let mut items: Vec<(String, Vec<u8>)> = Vec::new();
323 items.push(("Fat.class".to_string(), big.clone()));
324 for i in 0..300 {
325 items.push((format!("Tiny{i}.class"), format!("t{i}").into_bytes()));
326 }
327 let item_refs: Vec<(&str, &[u8])> =
328 items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
329 let jar = make_jar(&item_refs);
330
331 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
332 .unwrap()
333 .collect::<Result<Vec<_>, _>>()
334 .unwrap();
335
336 assert_eq!(entries.len(), items.len());
337 let got: std::collections::HashMap<&str, &[u8]> =
338 entries.iter().map(|e| (e.name.as_str(), e.data.as_slice())).collect();
339 for (name, data) in &items {
340 assert_eq!(got.get(name.as_str()), Some(&data.as_slice()), "entry {name} mismatch");
341 }
342 }
343}