Skip to content

Commit 68882b2

Browse files
feat: Implement by_path*() methods on ZipArchive (#382)
* Implement by_path*() methods. * Refactor by_path tests. * fix: test requires aes-crypto * fix: Move `use zip::AesMode` and declaration of `PASSWORD` into the test that uses them, since they're not used when the `aes-crypto` feature isn't enabled --------- Co-authored-by: Chris Hennick <[email protected]> Co-authored-by: hennickc <[email protected]>
1 parent 8a6e832 commit 68882b2

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

src/read.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,36 @@ impl<R: Read + Seek> ZipArchive<R> {
10211021
self.shared.files.get_index_of(name)
10221022
}
10231023

1024+
/// Search for a file entry by path, decrypt with given password
1025+
///
1026+
/// # Warning
1027+
///
1028+
/// The implementation of the cryptographic algorithms has not
1029+
/// gone through a correctness review, and you should assume it is insecure:
1030+
/// passwords used with this API may be compromised.
1031+
///
1032+
/// This function sometimes accepts wrong password. This is because the ZIP spec only allows us
1033+
/// to check for a 1/256 chance that the password is correct.
1034+
/// There are many passwords out there that will also pass the validity checks
1035+
/// we are able to perform. This is a weakness of the ZipCrypto algorithm,
1036+
/// due to its fairly primitive approach to cryptography.
1037+
pub fn by_path_decrypt<T: AsRef<Path>>(
1038+
&mut self,
1039+
path: T,
1040+
password: &[u8],
1041+
) -> ZipResult<ZipFile<'_, R>> {
1042+
self.index_for_path(path)
1043+
.ok_or(ZipError::FileNotFound)
1044+
.and_then(|index| self.by_index_with_optional_password(index, Some(password)))
1045+
}
1046+
1047+
/// Search for a file entry by path
1048+
pub fn by_path<T: AsRef<Path>>(&mut self, path: T) -> ZipResult<ZipFile<'_, R>> {
1049+
self.index_for_path(path)
1050+
.ok_or(ZipError::FileNotFound)
1051+
.and_then(|index| self.by_index_with_optional_password(index, None))
1052+
}
1053+
10241054
/// Get the index of a file entry by path, if it's present.
10251055
#[inline(always)]
10261056
pub fn index_for_path<T: AsRef<Path>>(&self, path: T) -> Option<usize> {

tests/by_path.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use std::io::{Cursor, Read, Write};
2+
use std::path::Path;
3+
use zip::read::ZipFile;
4+
use zip::write::SimpleFileOptions;
5+
use zip::{ZipArchive, ZipWriter};
6+
7+
const DIRECTORY_NAME: &str = "test_directory";
8+
const FILE_NAME: &str = "hello_world.txt";
9+
const LOREM_IPSUM: &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
10+
11+
#[test]
12+
fn by_path() {
13+
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
14+
let mut archive = create_archive(options);
15+
let path = Path::new(DIRECTORY_NAME).join(FILE_NAME);
16+
let file = archive.by_path(path).unwrap();
17+
validate_file(file);
18+
}
19+
20+
#[test]
21+
#[cfg(feature = "aes-crypto")]
22+
fn by_path_decrypt() {
23+
use zip::AesMode;
24+
25+
const PASSWORD: &str = "helloworld";
26+
27+
let options = SimpleFileOptions::default()
28+
.compression_method(zip::CompressionMethod::Stored)
29+
.with_aes_encryption(AesMode::Aes128, PASSWORD);
30+
let mut archive = create_archive(options);
31+
let path = Path::new(DIRECTORY_NAME).join(FILE_NAME);
32+
let file = archive.by_path_decrypt(path, PASSWORD.as_bytes()).unwrap();
33+
validate_file(file);
34+
}
35+
36+
fn create_archive(options: SimpleFileOptions) -> ZipArchive<Cursor<Vec<u8>>> {
37+
let mut buf = Vec::new();
38+
let mut zip = ZipWriter::new(Cursor::new(&mut buf));
39+
zip.add_directory(DIRECTORY_NAME, options).unwrap();
40+
zip.start_file(format!("{DIRECTORY_NAME}/{FILE_NAME}"), options)
41+
.unwrap();
42+
zip.write_all(LOREM_IPSUM).unwrap();
43+
zip.finish().unwrap();
44+
ZipArchive::new(Cursor::new(buf)).unwrap()
45+
}
46+
47+
fn validate_file<T>(mut file: ZipFile<T>)
48+
where
49+
T: Read,
50+
{
51+
let mut file_buf = Vec::new();
52+
file.read_to_end(&mut file_buf).unwrap();
53+
assert_eq!(LOREM_IPSUM, file_buf);
54+
}

0 commit comments

Comments
 (0)