Skip to content

Commit

Permalink
add ZipEntry API in src/unstable/read.rs
Browse files Browse the repository at this point in the history
- also use git dep for zstd to pull in removal of R: BufRead bound
- move new methods into unstable/read.rs
- support getting aes verification info from a raw ZipEntry
- flesh out other methods from ZipFile -> ZipEntry
- now create a streaming abstraction
- move some methods around
- make AesModeInfo pub(crate) to make its intention clear
- make ArchiveEntry more publicly accessible
- allow(private_interfaces) -> allow(warnings)
- fix private bounds on stable
  • Loading branch information
cosmicexplorer committed Aug 10, 2024
1 parent 3f6768e commit a3df4bb
Show file tree
Hide file tree
Showing 10 changed files with 1,191 additions and 134 deletions.
8 changes: 5 additions & 3 deletions src/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub struct AesReader<R> {
data_length: u64,
}

impl<R: Read> AesReader<R> {
impl<R> AesReader<R> {
pub const fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> {
let data_length = compressed_size
- (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64;
Expand All @@ -77,7 +77,9 @@ impl<R: Read> AesReader<R> {
data_length,
}
}
}

impl<R: Read> AesReader<R> {
/// Read the AES header bytes and validate the password.
///
/// Even if the validation succeeds, there is still a 1 in 65536 chance that an incorrect
Expand Down Expand Up @@ -150,7 +152,7 @@ impl<R: Read> AesReader<R> {
/// There is a 1 in 65536 chance that an invalid password passes that check.
/// After the data has been read and decrypted an HMAC will be checked and provide a final means
/// to check if either the password is invalid or if the data has been changed.
pub struct AesReaderValid<R: Read> {
pub struct AesReaderValid<R> {
reader: R,
data_remaining: u64,
cipher: Cipher,
Expand Down Expand Up @@ -214,7 +216,7 @@ impl<R: Read> Read for AesReaderValid<R> {
}
}

impl<R: Read> AesReaderValid<R> {
impl<R> AesReaderValid<R> {
/// Consumes this decoder, returning the underlying reader.
pub fn into_inner(self) -> R {
self.reader
Expand Down
104 changes: 104 additions & 0 deletions src/crc32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,110 @@ impl<R: Read> Read for Crc32Reader<R> {
}
}

pub(crate) mod non_crypto {
use std::io;
use std::io::prelude::*;

use crc32fast::Hasher;

/// Reader that validates the CRC32 when it reaches the EOF.
pub struct Crc32Reader<R> {
inner: R,
hasher: Hasher,
check: u32,
}

impl<R> Crc32Reader<R> {
/// 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<(), &'static str> {
let res = self.hasher.clone().finalize();
if self.check == res {
Ok(())
} else {
/* TODO: make this into our own Crc32Error error type! */
Err("Invalid checksum")
}
}
}

impl<R: Read> Read for Crc32Reader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
/* We want to make sure we only check the hash when the input stream is exhausted. */
if buf.is_empty() {
/* If the input buf is empty (this shouldn't happen, but isn't guaranteed), we
* still want to "pull" from the source in case it surfaces an i/o error. This will
* always return a count of Ok(0) if successful. */
return self.inner.read(buf);
}

let count = self.inner.read(buf)?;
if count == 0 {
return self
.check_matches()
.map(|()| 0)
/* TODO: use io::Error::other for MSRV >=1.74 */
.map_err(|e| io::Error::new(io::ErrorKind::Other, e));
}
self.hasher.update(&buf[..count]);
Ok(count)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_empty_reader() {
let data: &[u8] = b"";
let mut buf = [0; 1];

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

let mut reader = Crc32Reader::new(data, 1);
assert!(reader
.read(&mut buf)
.unwrap_err()
.to_string()
.contains("Invalid checksum"));
}

#[test]
fn test_byte_by_byte() {
let data: &[u8] = b"1234";
let mut buf = [0; 1];

let mut reader = Crc32Reader::new(data, 0x9be3e0a3);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 0);
// Can keep reading 0 bytes after the end
assert_eq!(reader.read(&mut buf).unwrap(), 0);
}

#[test]
fn test_zero_read() {
let data: &[u8] = b"1234";
let mut buf = [0; 5];

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

#[cfg(test)]
mod test {
use super::*;
Expand Down
Loading

0 comments on commit a3df4bb

Please sign in to comment.