Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Interpret data descriptors when reading zip file from (read, nonseek) stream #197

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
93a7f69
Added the method fn read_zipfile_from_seekablestream<S: Read + Seek>(…
0xCCF4 Jun 21, 2024
d146ff3
Merge branch 'refs/heads/master' into feature-read-from-seekable-stream
0xCCF4 Jun 21, 2024
7f0d07b
Merged master to feature branch
0xCCF4 Jun 21, 2024
59f1327
Moved streamed zip read tests to custom test file
0xCCF4 Jun 21, 2024
3bef659
Added security risk documentation and untrusted value struct to encap…
0xCCF4 Jun 22, 2024
f6b5da9
Library does not require Take<Read> anymore but instead accepts a tem…
0xCCF4 Jun 23, 2024
e36ad61
Merge branch 'refs/heads/master' into feature-read-from-seekable-stream
0xCCF4 Jun 23, 2024
47f718b
Completed merge master -> feature branch
0xCCF4 Jun 23, 2024
bf7a030
CRC32 checksum is now late propagated
0xCCF4 Jun 24, 2024
3265477
ZipStream API supports archives with data descriptor
0xCCF4 Jun 24, 2024
1d3afa9
Run cargo fmt --all
0xCCF4 Jun 24, 2024
ad3dbc0
chore: Feature-gate ReadAndSupplyExpectedCRC32 implementations
Pr0methean Jul 6, 2024
fc83a70
Merge branch 'master' into feature-read-from-seekable-stream
Pr0methean Jul 6, 2024
6cc20f5
refactor: applied simple review suggestions and cargo fmt & clippy
0xCCF4 Jul 8, 2024
c61a683
Merge remote-tracking branch 'origin/feature-read-from-seekable-strea…
0xCCF4 Jul 8, 2024
f0cb9f2
fix: completed merge of xz decoder into feature branch
0xCCF4 Jul 15, 2024
1ecad02
Merge branch 'refs/heads/master' into feature-read-from-seekable-stream
0xCCF4 Jul 15, 2024
549d1da
perf: use vecdequeue for look ahead buffer
0xCCF4 Jul 15, 2024
7cd7937
Merge branch 'master' into feature-read-from-seekable-stream
Pr0methean Jul 15, 2024
a530f52
Merge branch 'master' into feature-read-from-seekable-stream
Pr0methean Jul 19, 2024
6022136
fix: FixedSizeBlock must extend Pod
Pr0methean Jul 19, 2024
a9b7bc6
Merge branch 'master' into feature-read-from-seekable-stream
Pr0methean Aug 3, 2024
ecf5588
Merge branch 'master' into feature-read-from-seekable-stream
Pr0methean Aug 13, 2024
2ee9c18
Merge branch 'master' into feature-read-from-seekable-stream
Pr0methean Nov 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion examples/stdin_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ fn real_main() -> i32 {
let mut buf = [0u8; 16];

loop {
match zip::read::read_zipfile_from_stream(&mut stdin_handle) {
let file = match zip::read::read_zipfile_from_stream(&mut stdin_handle) {
Err(e) => {
println!("Error encountered while reading zip: {e:?}");
return 1;
}
Ok(value) => value,
};

match file.unwrap_or_error("data descriptors not supported while reading stdin") {
Ok(Some(mut file)) => {
println!(
"{}: {} bytes ({} bytes packed)",
Expand Down
4 changes: 4 additions & 0 deletions src/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ impl<R: Read> AesReaderValid<R> {
pub fn into_inner(self) -> R {
self.reader
}

pub fn get_ref(&self) -> &R {
&self.reader
}
}

pub struct AesWriter<W> {
Expand Down
137 changes: 122 additions & 15 deletions src/crc32.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,49 @@
//! Helper module to compute a CRC32 checksum

use bzip2::read::BzDecoder;
use std::io;
use std::io::prelude::*;
use std::io::BufReader;

use crate::read::lzma::LzmaDecoder;
use crate::read::CryptoReader;
use crc32fast::Hasher;
use deflate64::Deflate64Decoder;
use flate2::read::DeflateDecoder;

/// Reader that validates the CRC32 when it reaches the EOF.
pub struct Crc32Reader<R> {
pub struct Crc32Reader<R: ReadAndSupplyExpectedCRC32> {
inner: R,
hasher: Hasher,
check: u32,
/// Signals if `inner` stores aes encrypted data.
/// AE-2 encrypted data doesn't use crc and sets the value to 0.
ae2_encrypted: bool,
}

impl<R> Crc32Reader<R> {
impl<R: ReadAndSupplyExpectedCRC32> Crc32Reader<R> {
/// Get a new Crc32Reader 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<R> {
pub(crate) fn new(inner: R, ae2_encrypted: bool) -> Crc32Reader<R> {
Crc32Reader {
inner,
hasher: Hasher::new(),
check: checksum,
ae2_encrypted,
}
}

fn check_matches(&self) -> bool {
self.check == self.hasher.clone().finalize()
fn check_matches(&self) -> std::io::Result<bool> {
Ok(self.inner.get_crc32()? == self.hasher.clone().finalize())
}

pub fn into_inner(self) -> R {
self.inner
}
}

impl<R: Read> Read for Crc32Reader<R> {
impl<R: ReadAndSupplyExpectedCRC32> Read for Crc32Reader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let invalid_check = !buf.is_empty() && !self.check_matches() && !self.ae2_encrypted;

let count = match self.inner.read(buf) {
Ok(0) if invalid_check => {
Ok(0) if !buf.is_empty() && !self.check_matches()? && !self.ae2_encrypted => {
return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum"))
}
Ok(n) => n,
Expand All @@ -52,6 +54,111 @@ impl<R: Read> Read for Crc32Reader<R> {
}
}

/// A reader trait that provides a method to get the expected crc of the data read.
/// In the normal case, the expected crc is known before the zip entry is read.
/// In streaming mode with data descriptors, the crc will be available after the data is read.
/// Still in both cases the crc is available after the data is read and can be checked.
pub trait ReadAndSupplyExpectedCRC32: Read {
fn get_crc32(&self) -> std::io::Result<u32>;
}

pub struct InitiallyKnownCRC32<R: Read> {
reader: R,
crc: u32,
}

impl<R: Read> InitiallyKnownCRC32<R> {
pub fn new(reader: R, crc: u32) -> InitiallyKnownCRC32<R> {
InitiallyKnownCRC32 { reader, crc }
}

#[allow(dead_code)]
pub fn into_inner(self) -> R {
self.reader
}

#[allow(dead_code)]
pub fn get_ref(&self) -> &R {
&self.reader
}
}

impl<R: Read> Read for InitiallyKnownCRC32<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.reader.read(buf)
}
}

impl<R: Read> ReadAndSupplyExpectedCRC32 for InitiallyKnownCRC32<R> {
fn get_crc32(&self) -> std::io::Result<u32> {
Ok(self.crc)
}
}

impl<'a, T: ReadAndSupplyExpectedCRC32 + 'a> ReadAndSupplyExpectedCRC32 for CryptoReader<'a, T> {
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_crc32()
}
}

impl<T: ReadAndSupplyExpectedCRC32> ReadAndSupplyExpectedCRC32 for DeflateDecoder<T> {
Pr0methean marked this conversation as resolved.
Show resolved Hide resolved
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_crc32()
}
}

impl<T: ReadAndSupplyExpectedCRC32> ReadAndSupplyExpectedCRC32 for Deflate64Decoder<BufReader<T>> {
Pr0methean marked this conversation as resolved.
Show resolved Hide resolved
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_ref().get_crc32()
}
}

impl<T: ReadAndSupplyExpectedCRC32> ReadAndSupplyExpectedCRC32 for BzDecoder<T> {
Pr0methean marked this conversation as resolved.
Show resolved Hide resolved
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_crc32()
}
}

impl<'a, T: ReadAndSupplyExpectedCRC32 + BufRead> ReadAndSupplyExpectedCRC32
Pr0methean marked this conversation as resolved.
Show resolved Hide resolved
for zstd::Decoder<'a, T>
{
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_crc32()
}
}

impl<'a, T: ReadAndSupplyExpectedCRC32> ReadAndSupplyExpectedCRC32
Pr0methean marked this conversation as resolved.
Show resolved Hide resolved
for zstd::Decoder<'a, BufReader<T>>
{
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_ref().get_crc32()
}
}

impl<T: ReadAndSupplyExpectedCRC32> ReadAndSupplyExpectedCRC32 for LzmaDecoder<T> {
Pr0methean marked this conversation as resolved.
Show resolved Hide resolved
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_crc32()
}
}

impl<'a> ReadAndSupplyExpectedCRC32 for Box<(dyn ReadAndSupplyExpectedCRC32 + 'a)> {
fn get_crc32(&self) -> io::Result<u32> {
self.as_ref().get_crc32()
}
}

impl<'a, T: ReadAndSupplyExpectedCRC32 + 'a> ReadAndSupplyExpectedCRC32 for Box<T> {
fn get_crc32(&self) -> io::Result<u32> {
self.as_ref().get_crc32()
}
}

impl<T: ReadAndSupplyExpectedCRC32> ReadAndSupplyExpectedCRC32 for std::io::Take<&mut T> {
fn get_crc32(&self) -> io::Result<u32> {
self.get_ref().get_crc32()
}
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -61,10 +168,10 @@ mod test {
let data: &[u8] = b"";
let mut buf = [0; 1];

let mut reader = Crc32Reader::new(data, 0, false);
let mut reader = Crc32Reader::new(InitiallyKnownCRC32::new(data, 0), false);
assert_eq!(reader.read(&mut buf).unwrap(), 0);

let mut reader = Crc32Reader::new(data, 1, false);
let mut reader = Crc32Reader::new(InitiallyKnownCRC32::new(data, 1), false);
assert!(reader
.read(&mut buf)
.unwrap_err()
Expand All @@ -77,7 +184,7 @@ mod test {
let data: &[u8] = b"1234";
let mut buf = [0; 1];

let mut reader = Crc32Reader::new(data, 0x9be3e0a3, false);
let mut reader = Crc32Reader::new(InitiallyKnownCRC32::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);
Expand All @@ -92,7 +199,7 @@ mod test {
let data: &[u8] = b"1234";
let mut buf = [0; 5];

let mut reader = Crc32Reader::new(data, 0x9be3e0a3, false);
let mut reader = Crc32Reader::new(InitiallyKnownCRC32::new(data, 0x9be3e0a3), false);
assert_eq!(reader.read(&mut buf[..0]).unwrap(), 0);
assert_eq!(reader.read(&mut buf).unwrap(), 4);
}
Expand Down
Loading