1use crate::ZipReadOptions;
4use crate::extra_fields::ExtraFields;
5use crate::format::blocks::{FixedSizeBlock, Pod, ZipCentralEntryBlock, ZipLocalEntryBlock};
6use crate::format::magic::Magic;
7use crate::read::extract::{MAX_SYMLINK_TARGET_LEN, make_symlink_as_file};
8use crate::read::extract::{make_symlink, make_symlink_as_file_utf8};
9use crate::read::{
10 SymlinkExtractAction, ZipFile, ZipFileData, ZipFileEntry, ZipResult,
11 central_header_to_zip_file_inner,
12};
13use crate::result::{ZipError, invalid};
14
15use indexmap::IndexMap;
16use std::borrow::Cow;
17use std::io::{self, Read};
18use std::path::{Path, PathBuf};
19
20#[derive(Debug)]
22pub struct ZipStreamReader<R>(R);
23
24impl<R> ZipStreamReader<R> {
25 pub const fn new(reader: R) -> Self {
27 Self(reader)
28 }
29}
30
31fn parse_central_directory<R: Read>(reader: &mut R) -> ZipResult<ZipFileEntry<'static>> {
32 let archive_offset = 0;
35 let central_header_start = 0;
36
37 let block = ZipCentralEntryBlock::parse(reader)?;
39 let (file, file_name_raw) =
40 central_header_to_zip_file_inner(reader, archive_offset, central_header_start, block)?;
41 Ok(ZipFileEntry {
42 file_name_raw: Cow::Owned(file_name_raw),
43 data: Cow::Owned(file),
44 })
45}
46
47impl<R: Read> ZipStreamReader<R> {
48 pub fn visit<V: ZipStreamVisitor>(mut self, visitor: &mut V) -> ZipResult<()> {
51 while let Some(mut file) = read_zipfile_from_stream(&mut self.0)? {
52 visitor.visit_file(&mut file)?;
53 }
54
55 let signature = Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes();
59 let mut central_directory = (&signature[..]).chain(&mut self.0);
60 while let Ok(metadata) = parse_central_directory(&mut central_directory) {
61 visitor.visit_additional_metadata(&metadata)?;
62 }
63
64 Ok(())
65 }
66
67 pub fn extract<P: AsRef<Path>>(self, directory: P) -> ZipResult<()> {
77 struct Extractor {
80 outpath: PathBuf,
81 resolved_paths: IndexMap<Box<[u8]>, PathBuf>,
82 #[cfg(unix)]
83 file_permissions: std::collections::BTreeMap<PathBuf, u32>,
84 }
85 impl ZipStreamVisitor for Extractor {
86 fn visit_file<R: Read>(&mut self, file: &mut ZipFile<'_, R>) -> ZipResult<()> {
87 let extract_options = crate::read::extract::ExtractOptions::default();
88 let mut outpath = self.outpath.clone();
89 file.safe_prepare_path(
90 &self.outpath,
91 &mut outpath,
92 None::<&(_, fn(&Path) -> bool)>,
93 )?;
94 self.resolved_paths
95 .insert(file.name_raw().into(), outpath.clone());
96
97 if file.is_symlink() {
98 let declared_len = file.size();
107 if declared_len > MAX_SYMLINK_TARGET_LEN {
108 return Err(invalid!(
109 "symlink target declares {} bytes, more than the {} byte limit",
110 declared_len,
111 MAX_SYMLINK_TARGET_LEN
112 ));
113 }
114
115 let mut target = Vec::with_capacity(declared_len as usize);
116 file.read_to_end(&mut target)?;
117 match extract_options.symlink_action {
118 SymlinkExtractAction::ExtractAsFile => {
119 make_symlink_as_file(&outpath, &target)?;
120 }
121 SymlinkExtractAction::ExtractAsFileUtf8 => {
122 make_symlink_as_file_utf8(&outpath, &target)?;
123 }
124 _ => {
125 make_symlink(
126 &self.outpath,
127 &outpath,
128 &target,
129 &self.resolved_paths,
130 extract_options.symlink_action,
131 )?;
132 }
133 }
134 return Ok(());
135 }
136 if file.is_dir() {
137 fs::create_dir_all(&outpath)?;
138 } else {
139 let mut outfile = fs::File::create(&outpath)?;
140 io::copy(file, &mut outfile)?;
141 }
142
143 Ok(())
144 }
145
146 #[allow(unused)]
147 fn visit_additional_metadata(&mut self, metadata: &ZipFileEntry<'_>) -> ZipResult<()> {
148 #[cfg(unix)]
153 if let (Some(outpath), Some(mode)) = (
154 self.resolved_paths.get(metadata.name_raw()),
155 metadata.unix_mode(),
156 ) {
157 self.file_permissions.insert(outpath.clone(), mode);
158 }
159 Ok(())
160 }
161 }
162 use std::fs;
163 fs::create_dir_all(&directory)?;
164 let directory = directory.as_ref().canonicalize()?;
165
166 let mut extractor = Extractor {
167 outpath: directory,
168 resolved_paths: IndexMap::new(),
169 #[cfg(unix)]
170 file_permissions: Default::default(),
171 };
172 self.visit(&mut extractor)?;
173
174 #[cfg(unix)]
177 {
178 for (path, mode) in extractor.file_permissions.into_iter().rev() {
179 use std::os::unix::fs::PermissionsExt;
180 crate::read::extract::set_permissions_no_follow_symlink(
181 &path,
182 std::fs::Permissions::from_mode(mode),
183 )?;
184 }
185 }
186 Ok(())
187 }
188}
189
190pub trait ZipStreamVisitor {
192 fn visit_file<R: Read>(&mut self, file: &mut ZipFile<'_, R>) -> ZipResult<()>;
198
199 fn visit_additional_metadata(&mut self, metadata: &ZipFileEntry<'_>) -> ZipResult<()>;
203}
204
205pub fn read_zipfile_from_stream<R: Read>(reader: &mut R) -> ZipResult<Option<ZipFile<'_, R>>> {
222 read_zipfile_from_stream_with_options(reader, ZipReadOptions::default())
223}
224
225pub fn read_zipfile_from_stream_with_compressed_size<R: Read>(
228 reader: &mut R,
229 compressed_size: u64,
230) -> ZipResult<Option<ZipFile<'_, R>>> {
231 let options = ZipReadOptions::default().override_compressed_size(compressed_size);
232 read_zipfile_from_stream_with_options(reader, options)
233}
234
235pub fn read_zipfile_from_stream_with_options<'a, R: Read>(
238 reader: &'a mut R,
239 mut options: ZipReadOptions<'a>,
240) -> ZipResult<Option<ZipFile<'a, R>>> {
241 let mut magic_buf = [0; size_of::<u32>()];
245 reader.read_exact(&mut magic_buf)?;
246
247 match Magic::from_le_bytes(magic_buf) {
248 Magic::LOCAL_FILE_HEADER_SIGNATURE => (),
249 Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None),
250 _ => return Err(ZipLocalEntryBlock::WRONG_MAGIC_ERROR),
251 }
252
253 let mut block = ZipLocalEntryBlock::zeroed();
254 reader.read_exact(block.as_bytes_mut())?;
255
256 let block = block.from_le();
257 let file_name_length: usize = block.file_name_length.into();
259 let mut file_name_raw = vec![0u8; file_name_length];
260 if let Err(e) = reader.read_exact(&mut file_name_raw) {
261 if e.kind() == std::io::ErrorKind::UnexpectedEof {
262 return Err(invalid!("File name extends beyond file boundary"));
263 }
264 return Err(e.into());
265 }
266
267 let extra_field_length: usize = block.extra_field_length.into();
269 let mut extra_fields_raw = vec![0u8; extra_field_length];
270 if let Err(e) = reader.read_exact(&mut extra_fields_raw) {
271 if e.kind() == std::io::ErrorKind::UnexpectedEof {
272 return Err(invalid!("Extra field extends beyond file boundary"));
273 }
274 return Err(e.into());
275 }
276 let extra_fields = ExtraFields::parse(&extra_fields_raw, &block, true)?;
278 let mut data = ZipFileData::from_local_block(block, extra_fields)?;
279 data.apply_extra_fields(&mut file_name_raw)?;
280 if data.is_using_data_descriptor() {
281 if let Some(comp_size) = options.force_compressed_size {
282 data.compressed_size = comp_size;
283 } else {
284 return Err(ZipError::UnsupportedArchive(
285 "The file length is not available in the local header",
286 ));
287 }
288 }
289 if let Some(uncomp_size) = options.force_uncompressed_size {
290 data.uncompressed_size = uncomp_size;
291 } else if data.is_using_data_descriptor() {
292 options.size_unknown = true;
294 }
295 if let Some(crc) = options.force_crc {
296 data.crc32 = crc;
297 }
298
299 let limit_reader = reader.take(data.compressed_size);
300 let reader = data.make_reader(limit_reader, options)?;
301 Ok(Some(ZipFile {
302 file_name_raw: Cow::Owned(file_name_raw),
303 data: Cow::Owned(data),
304 reader,
305 }))
306}
307
308#[cfg(test)]
309mod tests {
310 use crate::read::ZipFile;
311 use crate::read::ZipFileEntry;
312 use crate::read::stream::{ZipStreamReader, ZipStreamVisitor};
313 use crate::result::ZipResult;
314 use std::collections::BTreeSet;
315 use std::io::{Cursor, Read};
316
317 #[test]
318 fn zip_read_streaming_visitor() {
319 let reader =
320 ZipStreamReader::new(Cursor::new(include_bytes!("../../tests/data/mimetype.zip")));
321
322 #[derive(Default)]
323 struct V {
324 filenames: BTreeSet<Box<str>>,
325 }
326 impl ZipStreamVisitor for V {
327 fn visit_file<R: Read>(&mut self, file: &mut ZipFile<'_, R>) -> ZipResult<()> {
328 if file.is_file() {
329 let file_name = file.name().unwrap();
330 self.filenames.insert(file_name.into());
331 }
332
333 Ok(())
334 }
335 fn visit_additional_metadata(
336 &mut self,
337 zip_file_metadata: &ZipFileEntry<'_>,
338 ) -> ZipResult<()> {
339 if zip_file_metadata.is_file() {
340 let file_name = zip_file_metadata.name().unwrap();
341 assert!(
342 self.filenames.contains::<str>(file_name.as_ref()),
343 "{} is missing its file content",
344 file_name
345 );
346 }
347
348 Ok(())
349 }
350 }
351
352 reader.visit(&mut V::default()).unwrap();
353 }
354
355 #[test]
356 fn file_and_dir_predicates() {
357 let reader = ZipStreamReader::new(Cursor::new(include_bytes!(
358 "../../tests/data/files_and_dirs.zip"
359 )));
360
361 #[derive(Default)]
362 struct V {
363 filenames: BTreeSet<Box<str>>,
364 }
365 impl ZipStreamVisitor for V {
366 fn visit_file<R: Read>(&mut self, file: &mut ZipFile<'_, R>) -> ZipResult<()> {
367 let full_name = file.enclosed_name().unwrap();
368 let file_name = full_name.file_name().unwrap().to_str().unwrap();
369 assert!(
370 (file_name.starts_with("dir") && file.is_dir())
371 || (file_name.starts_with("file") && file.is_file())
372 );
373
374 if file.is_file() {
375 let file_name = file.name().unwrap();
376 self.filenames.insert(file_name.into());
377 }
378
379 Ok(())
380 }
381 fn visit_additional_metadata(
382 &mut self,
383 zip_file_metadata: &ZipFileEntry<'_>,
384 ) -> ZipResult<()> {
385 if zip_file_metadata.is_file() {
386 let file_name = zip_file_metadata.name().unwrap();
387 assert!(
388 self.filenames.contains::<str>(file_name.as_ref()),
389 "{} is missing its file content",
390 file_name
391 );
392 }
393
394 Ok(())
395 }
396 }
397
398 reader.visit(&mut V::default()).unwrap();
399 }
400
401 #[cfg(not(miri))]
404 #[test]
405 fn test_cannot_symlink_outside_destination() -> ZipResult<()> {
406 use crate::ZipWriter;
407 use crate::write::SimpleFileOptions;
408 use std::fs::create_dir;
409 use tempfile::TempDir;
410
411 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
412 writer.add_symlink("symlink/", "../dest-sibling/", SimpleFileOptions::default())?;
413 writer.start_file("symlink/dest-file", SimpleFileOptions::default())?;
414 let reader = ZipStreamReader::new(writer.finish()?);
415 let dest_parent = TempDir::with_prefix("stream__cannot_symlink_outside_destination")?;
416 let dest_sibling = dest_parent.path().join("dest-sibling");
417 create_dir(&dest_sibling)?;
418 let dest = dest_parent.path().join("dest");
419 create_dir(&dest)?;
420 assert!(reader.extract(dest).is_err());
421 assert!(!dest_sibling.join("dest-file").exists());
422 Ok(())
423 }
424
425 #[test]
436 fn stream_does_not_expose_the_symlink_bit() -> ZipResult<()> {
437 use crate::ZipWriter;
438 use crate::write::SimpleFileOptions;
439
440 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
441 writer.add_symlink("link", "/target", SimpleFileOptions::default())?;
442 let mut cur = writer.finish()?;
443 cur.set_position(0);
444
445 let file = crate::read::read_zipfile_from_stream(&mut cur)?.unwrap();
446 assert!(
447 !file.is_symlink(),
448 "the stream path now sees the symlink bit -- the MAX_SYMLINK_TARGET_LEN \
449 guard in `stream.rs` is reachable and needs a regression test"
450 );
451
452 Ok(())
453 }
454
455 #[cfg(not(miri))]
457 #[test]
458 fn test_can_create_destination() -> ZipResult<()> {
459 use tempfile::TempDir;
460
461 let v = include_bytes!("../../tests/data/mimetype.zip");
462 let reader = ZipStreamReader::new(v.as_ref());
463 let dest = TempDir::with_prefix("stream_test_can_create_destination").unwrap();
464 reader.extract(&dest)?;
465 assert!(dest.path().join("mimetype").exists());
466 Ok(())
467 }
468
469 #[test]
470 fn zip_read_streaming() {
471 use super::read_zipfile_from_stream;
472
473 let mut reader = Cursor::new(include_bytes!("../../tests/data/mimetype.zip"));
474 loop {
475 if read_zipfile_from_stream(&mut reader).unwrap().is_none() {
476 break;
477 }
478 }
479 }
480
481 #[test]
482 #[cfg(feature = "deflate")]
483 fn zip_read_streaming_compressed() {
484 use super::read_zipfile_from_stream_with_compressed_size;
485 use crate::ZipWriter;
486 use crate::write::SimpleFileOptions;
487 use std::io::Write;
488
489 let compression_method = crate::CompressionMethod::Deflated;
490 let options = SimpleFileOptions::default()
491 .compression_method(compression_method)
492 .unix_permissions(0o755);
493
494 let mut bytes = Vec::new();
495 let mut writer = ZipWriter::new(std::io::Cursor::new(&mut bytes));
496 writer.start_file("file.txt", options).unwrap();
497 write!(&mut writer, "{}", "test-".repeat(100)).unwrap();
498 writer.finish().unwrap();
499
500 let compressed_size = u32::from_le_bytes(bytes[18..22].try_into().unwrap());
501 let uncompressed_size = u32::from_le_bytes(bytes[22..26].try_into().unwrap());
502
503 assert_eq!(compressed_size, 14);
504 assert_eq!(uncompressed_size as usize, "test-".len() * 100);
505
506 let mut reader = Cursor::new(bytes);
507 loop {
508 if read_zipfile_from_stream_with_compressed_size(
509 &mut reader,
510 u64::from(compressed_size),
511 )
512 .unwrap()
513 .is_none()
514 {
515 break;
516 }
517 }
518 }
519
520 #[test]
521 #[cfg(feature = "aes-crypto")]
522 fn zip_read_streaming_compressed_and_aes() {
523 use super::read_zipfile_from_stream_with_options;
524 use crate::ZipReadOptions;
525
526 let bytes = include_bytes!("../../tests/data/aes_archive.zip");
527 let compressed_size = 46;
528
529 let mut reader = Cursor::new(bytes);
530 const PASSWORD: &[u8] = b"helloworld";
531 let options = ZipReadOptions::new()
532 .password(Some(PASSWORD))
533 .override_compressed_size(compressed_size);
534
535 let result = read_zipfile_from_stream_with_options(&mut reader, options);
537 let optional_file = result.unwrap();
538 let mut file = optional_file.unwrap();
539
540 let file_name = file.name().unwrap();
541 assert_eq!(file_name, "secret_data_128");
542
543 const SECRET_CONTENT: &str = "Lorem ipsum dolor sit amet";
544 let mut decrypted_content = String::new();
545 file.read_to_string(&mut decrypted_content)
546 .expect("couldn't read encrypted file");
547 assert_eq!(SECRET_CONTENT, decrypted_content);
548 }
549
550 #[test]
551 #[cfg(feature = "aes-crypto")]
552 fn zip_read_streaming_compressed_and_aes_without_size() {
553 use super::read_zipfile_from_stream_with_options;
554 use crate::ZipReadOptions;
555
556 let bytes = include_bytes!("../../tests/data/aes_archive.zip");
557
558 let mut reader = Cursor::new(bytes);
559 const PASSWORD: &[u8] = b"helloworld";
560 let options = ZipReadOptions::new().password(Some(PASSWORD));
561
562 let result = read_zipfile_from_stream_with_options(&mut reader, options);
564 let optional_file = result.unwrap();
565 let mut file = optional_file.unwrap();
566
567 let file_name = file.name().unwrap();
568 assert_eq!(file_name, "secret_data_128");
569
570 const SECRET_CONTENT: &str = "Lorem ipsum dolor sit amet";
571 let mut decrypted_content = String::new();
572 file.read_to_string(&mut decrypted_content)
573 .expect("couldn't read encrypted file");
574 assert_eq!(SECRET_CONTENT, decrypted_content);
575 }
576
577 #[test]
578 fn zip_read_streaming_zipwriter() {
579 use crate::CompressionMethod;
580 use crate::ZipReadOptions;
581 use crate::ZipWriter;
582 use crate::read::read_zipfile_from_stream;
583 use crate::read::read_zipfile_from_stream_with_compressed_size;
584 use crate::read::read_zipfile_from_stream_with_options;
585 use crate::write::SimpleFileOptions;
586 use std::io::Write;
587
588 let mut buffer = Vec::new();
589 let options = SimpleFileOptions::default().compression_method(CompressionMethod::Stored);
590 let mut archive = ZipWriter::new_stream(Cursor::new(&mut buffer));
591 archive.start_file("name", options).unwrap();
592 archive.write_all(b"test").unwrap();
593 let compressed_size = b"test".len() as u64;
594 archive.finish().unwrap();
595 {
596 let mut reader = Cursor::new(buffer.clone());
598 let result = read_zipfile_from_stream(&mut reader);
599 assert!(result.is_err());
600 }
601 {
602 let mut reader = Cursor::new(buffer.clone());
604 let result =
605 read_zipfile_from_stream_with_compressed_size(&mut reader, compressed_size);
606 let optional_file = result.unwrap();
607 let mut file = optional_file.unwrap();
608 let file_name = file.name().unwrap();
609 assert_eq!(file_name, "name");
610 let mut content = Vec::new();
612 let read_result = file.read_to_end(&mut content);
613 assert!(read_result.is_err()); let error_str = read_result.unwrap_err().to_string();
616 assert!(error_str.contains("Invalid checksum"));
618 }
619 {
620 let mut reader = Cursor::new(buffer.clone());
622 let read_options = ZipReadOptions::default()
623 .override_compressed_size(compressed_size)
624 .override_crc(3632233996);
625 let result = read_zipfile_from_stream_with_options(&mut reader, read_options);
626 let optional_file = result.unwrap();
627 let mut file = optional_file.unwrap();
628 let file_name = file.name().unwrap();
629 assert_eq!(file_name, "name");
630 let mut content = Vec::new();
632 file.read_to_end(&mut content).unwrap();
633 assert_eq!(content, b"test");
634 }
635 {
636 let mut reader = Cursor::new(buffer.clone());
638 let read_options = ZipReadOptions::default()
639 .override_compressed_size(compressed_size)
640 .ignore_crc32(true);
641 let result = read_zipfile_from_stream_with_options(&mut reader, read_options);
642 let optional_file = result.unwrap();
643 let mut file = optional_file.unwrap();
644 let file_name = file.name().unwrap();
645 assert_eq!(file_name, "name");
646 let mut content = Vec::new();
648 let read_result = file.read_to_end(&mut content);
649 assert!(read_result.is_ok());
650 assert_eq!(content, b"test");
651 }
652 }
653}