Skip to content

Commit 020a61a

Browse files
add ZipEntry API in src/unstable/read.rs
- 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
1 parent 8656826 commit 020a61a

9 files changed

Lines changed: 1119 additions & 99 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ time = { workspace = true, optional = true, features = [
4444
"std",
4545
] }
4646
zeroize = { version = "1.8.1", optional = true, features = ["zeroize_derive"] }
47-
zstd = { version = "0.13.1", optional = true, default-features = false }
47+
# zstd = { version = "0.13.1", optional = true, default-features = false }
48+
zstd = { git = "https://github.com/gyscos/zstd-rs", rev = "a3738d680542e34b5529207ee30491ed7b69ed71", optional = true, default-features = false }
4849
zopfli = { version = "0.8.1", optional = true }
4950
deflate64 = { version = "0.1.8", optional = true }
5051
lzma-rs = { version = "0.3.0", default-features = false, optional = true }

src/aes.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub struct AesReader<R> {
6666
data_length: u64,
6767
}
6868

69-
impl<R: Read> AesReader<R> {
69+
impl<R> AesReader<R> {
7070
pub const fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> {
7171
let data_length = compressed_size
7272
- (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64;
@@ -77,7 +77,9 @@ impl<R: Read> AesReader<R> {
7777
data_length,
7878
}
7979
}
80+
}
8081

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

217-
impl<R: Read> AesReaderValid<R> {
219+
impl<R> AesReaderValid<R> {
218220
/// Consumes this decoder, returning the underlying reader.
219221
pub fn into_inner(self) -> R {
220222
self.reader

src/crc32.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,114 @@ impl<R: Read> Read for Crc32Reader<R> {
8383
}
8484
}
8585

86+
pub(crate) mod non_crypto {
87+
use std::io;
88+
use std::io::prelude::*;
89+
90+
use crc32fast::Hasher;
91+
92+
/// Reader that validates the CRC32 when it reaches the EOF.
93+
pub struct Crc32Reader<R> {
94+
inner: R,
95+
hasher: Hasher,
96+
check: u32,
97+
}
98+
99+
impl<R> Crc32Reader<R> {
100+
/// Get a new Crc32Reader which checks the inner reader against checksum.
101+
pub(crate) fn new(inner: R, checksum: u32) -> Self {
102+
Crc32Reader {
103+
inner,
104+
hasher: Hasher::new(),
105+
check: checksum,
106+
}
107+
}
108+
109+
fn check_matches(&self) -> Result<(), &'static str> {
110+
let res = self.hasher.clone().finalize();
111+
if self.check == res {
112+
Ok(())
113+
} else {
114+
/* TODO: make this into our own Crc32Error error type! */
115+
Err("Invalid checksum")
116+
}
117+
}
118+
119+
pub fn into_inner(self) -> R {
120+
self.inner
121+
}
122+
}
123+
124+
impl<R: Read> Read for Crc32Reader<R> {
125+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
126+
/* We want to make sure we only check the hash when the input stream is exhausted. */
127+
if buf.is_empty() {
128+
/* If the input buf is empty (this shouldn't happen, but isn't guaranteed), we
129+
* still want to "pull" from the source in case it surfaces an i/o error. This will
130+
* always return a count of Ok(0) if successful. */
131+
return self.inner.read(buf);
132+
}
133+
134+
let count = self.inner.read(buf)?;
135+
if count == 0 {
136+
return self
137+
.check_matches()
138+
.map(|()| 0)
139+
/* TODO: use io::Error::other for MSRV >=1.74 */
140+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e));
141+
}
142+
self.hasher.update(&buf[..count]);
143+
Ok(count)
144+
}
145+
}
146+
147+
#[cfg(test)]
148+
mod test {
149+
use super::*;
150+
151+
#[test]
152+
fn test_empty_reader() {
153+
let data: &[u8] = b"";
154+
let mut buf = [0; 1];
155+
156+
let mut reader = Crc32Reader::new(data, 0);
157+
assert_eq!(reader.read(&mut buf).unwrap(), 0);
158+
159+
let mut reader = Crc32Reader::new(data, 1);
160+
assert!(reader
161+
.read(&mut buf)
162+
.unwrap_err()
163+
.to_string()
164+
.contains("Invalid checksum"));
165+
}
166+
167+
#[test]
168+
fn test_byte_by_byte() {
169+
let data: &[u8] = b"1234";
170+
let mut buf = [0; 1];
171+
172+
let mut reader = Crc32Reader::new(data, 0x9be3e0a3);
173+
assert_eq!(reader.read(&mut buf).unwrap(), 1);
174+
assert_eq!(reader.read(&mut buf).unwrap(), 1);
175+
assert_eq!(reader.read(&mut buf).unwrap(), 1);
176+
assert_eq!(reader.read(&mut buf).unwrap(), 1);
177+
assert_eq!(reader.read(&mut buf).unwrap(), 0);
178+
// Can keep reading 0 bytes after the end
179+
assert_eq!(reader.read(&mut buf).unwrap(), 0);
180+
}
181+
182+
#[test]
183+
fn test_zero_read() {
184+
let data: &[u8] = b"1234";
185+
let mut buf = [0; 5];
186+
187+
let mut reader = Crc32Reader::new(data, 0x9be3e0a3);
188+
assert_eq!(reader.read(&mut buf[..0]).unwrap(), 0);
189+
assert_eq!(reader.read(&mut buf).unwrap(), 4);
190+
}
191+
}
192+
}
193+
86194
#[cfg(test)]
87195
mod test {
88196
use super::*;

0 commit comments

Comments
 (0)