diff --git a/src/crc32.rs b/src/crc32.rs index 7152c0853..a17cf84e5 100644 --- a/src/crc32.rs +++ b/src/crc32.rs @@ -4,9 +4,11 @@ use std::io; use std::io::prelude::*; use crc32fast::Hasher; +use displaydoc::Display; +use thiserror::Error; /// Reader that validates the CRC32 when it reaches the EOF. -pub struct Crc32Reader { +pub struct Crc32AesReader { inner: R, hasher: Hasher, check: u32, @@ -15,11 +17,11 @@ pub struct Crc32Reader { ae2_encrypted: bool, } -impl Crc32Reader { - /// Get a new Crc32Reader which checks the inner reader against checksum. +impl Crc32AesReader { + /// Get a new Crc32AesReader which checks the inner reader against checksum. /// The check is disabled if `ae2_encrypted == true`. - pub(crate) fn new(inner: R, checksum: u32, ae2_encrypted: bool) -> Crc32Reader { - Crc32Reader { + pub(crate) fn new(inner: R, checksum: u32, ae2_encrypted: bool) -> Crc32AesReader { + Crc32AesReader { inner, hasher: Hasher::new(), check: checksum, @@ -36,7 +38,7 @@ impl Crc32Reader { } } -impl Read for Crc32Reader { +impl Read for Crc32AesReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let invalid_check = !buf.is_empty() && !self.check_matches() && !self.ae2_encrypted; @@ -52,6 +54,62 @@ impl Read for Crc32Reader { } } +/// Errors from crc32 calculation. +#[derive(Debug, Display, Error)] +pub enum Crc32Error { + /// invalid checksum: expected {0}, got {1} + InvalidChecksum(u32, u32), +} + +/// Reader that validates the CRC32 when it reaches the EOF. +pub struct Crc32Reader { + inner: R, + hasher: Hasher, + check: u32, +} + +impl Crc32Reader { + /// Get a new Crc32Reader which checks the inner reader against checksum. + pub(crate) fn new(inner: R, checksum: u32) -> Self { + Crc32Reader { + inner, + hasher: Hasher::new(), + check: checksum, + } + } + + fn check_matches(&self) -> Result<(), Crc32Error> { + let res = self.hasher.clone().finalize(); + if self.check == res { + Ok(()) + } else { + Err(Crc32Error::InvalidChecksum(self.check, res)) + } + } + + pub fn into_inner(self) -> R { + self.inner + } +} + +impl Read for Crc32Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if buf.is_empty() { + return self.inner.read(buf).inspect(|n| assert_eq!(*n, 0)); + } + + let count = self.inner.read(buf)?; + if count == 0 { + return self + .check_matches() + .map(|()| 0) + .map_err(|e| io::Error::other(e)); + } + self.hasher.update(&buf[0..count]); + Ok(count) + } +} + #[cfg(test)] mod test { use super::*; @@ -61,10 +119,10 @@ mod test { let data: &[u8] = b""; let mut buf = [0; 1]; - let mut reader = Crc32Reader::new(data, 0, false); + let mut reader = Crc32AesReader::new(data, 0, false); assert_eq!(reader.read(&mut buf).unwrap(), 0); - let mut reader = Crc32Reader::new(data, 1, false); + let mut reader = Crc32AesReader::new(data, 1, false); assert!(reader .read(&mut buf) .unwrap_err() @@ -77,7 +135,7 @@ mod test { let data: &[u8] = b"1234"; let mut buf = [0; 1]; - let mut reader = Crc32Reader::new(data, 0x9be3e0a3, false); + let mut reader = Crc32AesReader::new(data, 0x9be3e0a3, false); assert_eq!(reader.read(&mut buf).unwrap(), 1); assert_eq!(reader.read(&mut buf).unwrap(), 1); assert_eq!(reader.read(&mut buf).unwrap(), 1); @@ -92,7 +150,7 @@ mod test { let data: &[u8] = b"1234"; let mut buf = [0; 5]; - let mut reader = Crc32Reader::new(data, 0x9be3e0a3, false); + let mut reader = Crc32AesReader::new(data, 0x9be3e0a3, false); assert_eq!(reader.read(&mut buf[..0]).unwrap(), 0); assert_eq!(reader.read(&mut buf).unwrap(), 4); } diff --git a/src/read.rs b/src/read.rs index 36ecd7a8c..c170667c2 100644 --- a/src/read.rs +++ b/src/read.rs @@ -4,7 +4,7 @@ use crate::aes::{AesReader, AesReaderValid}; use crate::compression::CompressionMethod; use crate::cp437::FromCp437; -use crate::crc32::Crc32Reader; +use crate::crc32::{Crc32AesReader, Crc32Reader}; use crate::extra_fields::{ExtendedTimestamp, ExtraField}; use crate::read::zip_archive::{Shared, SharedBuilder}; use crate::result::{ZipError, ZipResult}; @@ -178,20 +178,57 @@ impl<'a> CryptoReader<'a> { } } +pub(crate) enum EntryReader { + Raw(R), + Stored(Crc32Reader), + #[cfg(feature = "_deflate-any")] + Deflated(Crc32Reader>), + #[cfg(feature = "deflate64")] + Deflate64(Crc32Reader>>), + #[cfg(feature = "bzip2")] + Bzip2(Crc32Reader>), + /* #[cfg(feature = "zstd")] */ + /* Zstd(Crc32Reader>>), */ + #[cfg(feature = "lzma")] + Lzma(Crc32Reader>>), +} + +impl Read for EntryReader +where + R: Read, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + Self::Raw(r) => r.read(buf), + Self::Stored(r) => r.read(buf), + #[cfg(feature = "_deflate-any")] + Self::Deflated(r) => r.read(buf), + #[cfg(feature = "deflate64")] + Self::Deflate64(r) => r.read(buf), + #[cfg(feature = "bzip2")] + Self::Bzip2(r) => r.read(buf), + /* #[cfg(feature = "zstd")] */ + /* Self::Zstd(r) => r.read(buf), */ + #[cfg(feature = "lzma")] + Self::Lzma(r) => r.read(buf), + } + } +} + pub(crate) enum ZipFileReader<'a> { NoReader, Raw(io::Take<&'a mut dyn Read>), - Stored(Crc32Reader>), + Stored(Crc32AesReader>), #[cfg(feature = "_deflate-any")] - Deflated(Crc32Reader>>), + Deflated(Crc32AesReader>>), #[cfg(feature = "deflate64")] - Deflate64(Crc32Reader>>>), + Deflate64(Crc32AesReader>>>), #[cfg(feature = "bzip2")] - Bzip2(Crc32Reader>>), + Bzip2(Crc32AesReader>>), #[cfg(feature = "zstd")] - Zstd(Crc32Reader>>>), + Zstd(Crc32AesReader>>>), #[cfg(feature = "lzma")] - Lzma(Crc32Reader>>>), + Lzma(Crc32AesReader>>>), } impl<'a> Read for ZipFileReader<'a> { @@ -348,7 +385,7 @@ pub(crate) fn make_reader( let ae2_encrypted = reader.is_ae2_encrypted(); match compression_method { - CompressionMethod::Stored => Ok(ZipFileReader::Stored(Crc32Reader::new( + CompressionMethod::Stored => Ok(ZipFileReader::Stored(Crc32AesReader::new( reader, crc32, ae2_encrypted, @@ -356,7 +393,7 @@ pub(crate) fn make_reader( #[cfg(feature = "_deflate-any")] CompressionMethod::Deflated => { let deflate_reader = DeflateDecoder::new(reader); - Ok(ZipFileReader::Deflated(Crc32Reader::new( + Ok(ZipFileReader::Deflated(Crc32AesReader::new( deflate_reader, crc32, ae2_encrypted, @@ -365,7 +402,7 @@ pub(crate) fn make_reader( #[cfg(feature = "deflate64")] CompressionMethod::Deflate64 => { let deflate64_reader = Deflate64Decoder::new(reader); - Ok(ZipFileReader::Deflate64(Crc32Reader::new( + Ok(ZipFileReader::Deflate64(Crc32AesReader::new( deflate64_reader, crc32, ae2_encrypted, @@ -374,7 +411,7 @@ pub(crate) fn make_reader( #[cfg(feature = "bzip2")] CompressionMethod::Bzip2 => { let bzip2_reader = BzDecoder::new(reader); - Ok(ZipFileReader::Bzip2(Crc32Reader::new( + Ok(ZipFileReader::Bzip2(Crc32AesReader::new( bzip2_reader, crc32, ae2_encrypted, @@ -383,7 +420,7 @@ pub(crate) fn make_reader( #[cfg(feature = "zstd")] CompressionMethod::Zstd => { let zstd_reader = ZstdDecoder::new(reader).unwrap(); - Ok(ZipFileReader::Zstd(Crc32Reader::new( + Ok(ZipFileReader::Zstd(Crc32AesReader::new( zstd_reader, crc32, ae2_encrypted, @@ -392,7 +429,7 @@ pub(crate) fn make_reader( #[cfg(feature = "lzma")] CompressionMethod::Lzma => { let reader = LzmaDecoder::new(reader); - Ok(ZipFileReader::Lzma(Crc32Reader::new( + Ok(ZipFileReader::Lzma(Crc32AesReader::new( Box::new(reader), crc32, ae2_encrypted, @@ -402,6 +439,65 @@ pub(crate) fn make_reader( } } +pub(crate) fn find_entry_content(data: &ZipFileData, mut reader: R) -> ZipResult> +where + R: Read + Seek, +{ + // TODO: use .get_or_try_init() once stabilized to provide a closure returning a Result! + let data_start = match data.data_start.get() { + Some(data_start) => *data_start, + None => find_data_start(data, &mut reader)?, + }; + + reader.seek(io::SeekFrom::Start(data_start))?; + Ok(reader.take(data.compressed_size)) +} + +pub(crate) fn make_entry_reader( + compression_method: CompressionMethod, + crc32: u32, + mut reader: R, +) -> ZipResult> +where + R: Read, +{ + match compression_method { + CompressionMethod::Stored => Ok(EntryReader::Stored(Crc32Reader::new(reader, crc32))), + #[cfg(feature = "_deflate-any")] + CompressionMethod::Deflated => { + let deflate_reader = DeflateDecoder::new(reader); + Ok(EntryReader::Deflated(Crc32Reader::new( + deflate_reader, + crc32, + ))) + } + #[cfg(feature = "deflate64")] + CompressionMethod::Deflate64 => { + let deflate64_reader = Deflate64Decoder::new(reader); + Ok(EntryReader::Deflate64(Crc32Reader::new( + deflate64_reader, + crc32, + ))) + } + #[cfg(feature = "bzip2")] + CompressionMethod::Bzip2 => { + let bzip2_reader = BzDecoder::new(reader); + Ok(EntryReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32))) + } + /* #[cfg(feature = "zstd")] */ + /* CompressionMethod::Zstd => { */ + /* let zstd_reader = ZstdDecoder::new(reader).unwrap(); */ + /* Ok(EntryReader::Zstd(Crc32Reader::new(zstd_reader, crc32))) */ + /* } */ + #[cfg(feature = "lzma")] + CompressionMethod::Lzma => { + let reader = LzmaDecoder::new(reader); + Ok(EntryReader::Lzma(Crc32Reader::new(Box::new(reader), crc32))) + } + _ => Err(UnsupportedArchive("Compression method not supported")), + } +} + #[derive(Debug)] pub(crate) struct CentralDirectoryInfo { pub(crate) archive_offset: u64, @@ -910,6 +1006,8 @@ impl ZipArchive { } let symlink_target = if file.is_symlink() && (cfg!(unix) || cfg!(windows)) { let mut target = Vec::with_capacity(file.size() as usize); + /* FIXME: this is broken: needs to be .read_to_end(), otherwise it writes into an + * empty slice. */ file.read_exact(&mut target)?; Some(target) } else {