use crate::error::{MyError, MyResult};
use crate::finder::count_components;
use crate::fs::entry::{Entry, EntryResult};
use crate::fs::file::Signature;
use crate::fs::flags::FileFlags;
use crate::regex_insensitive;
use crate::util::file;
#[cfg(windows)]
use crate::util::version;
use crate::zip::manager::PasswordManager;
use crate::zip::parent::ZipParent;
use chrono::{Local, TimeZone};
use path_clean::PathClean;
use std::cell::RefCell;
use std::cmp;
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufReader;
#[cfg(windows)]
use std::io::Read;
use std::ops::DerefMut;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[cfg(unix)]
use uzers::{gid_t, uid_t};
use zip::read::ZipFile;
use zip::result::ZipError;
use zip::ZipArchive;
struct PkZipEntry<'a> {
zip_file: RefCell<ZipFile<'a, BufReader<File>>>,
zip_buffer: RefCell<Vec<u8>>,
file_path: PathBuf,
file_depth: usize,
inner_path: PathBuf,
inner_depth: usize,
#[cfg(unix)]
owner_uid: uid_t,
#[cfg(unix)]
owner_gid: gid_t,
}
impl<'a> PkZipEntry<'a> {
fn new(
zip_parent: &ZipParent,
zip_file: ZipFile<'a, BufReader<File>>,
) -> Self {
let (file_path, file_depth, inner_path, inner_depth) = Self::prepare_path(zip_parent, &zip_file);
let zip_file = RefCell::new(zip_file);
let zip_buffer = RefCell::new(Vec::new());
#[cfg(unix)]
let owner_uid = zip_parent.uid();
#[cfg(unix)]
let owner_gid = zip_parent.gid();
Self {
zip_file,
zip_buffer,
file_path,
file_depth,
inner_path,
inner_depth,
#[cfg(unix)]
owner_uid,
#[cfg(unix)]
owner_gid,
}
}
fn prepare_path(
zip_parent: &ZipParent,
zip_file: &ZipFile<BufReader<File>>,
) -> (PathBuf, usize, PathBuf, usize) {
let inner_path = zip_file.enclosed_name().unwrap_or_default().clean();
let inner_depth = count_components(&inner_path);
let file_depth = zip_parent.depth() + inner_depth;
let file_path = zip_parent.path().join(&inner_path);
(file_path, file_depth, inner_path, inner_depth)
}
}
impl<'a> Entry for PkZipEntry<'a> {
fn file_path(&self) -> &Path {
&self.file_path
}
fn file_name(&self) -> &OsStr {
self.file_path.file_name().unwrap_or_default()
}
fn file_depth(&self) -> usize {
self.file_depth
}
fn inner_path(&self) -> Option<&Path> {
Some(&self.inner_path)
}
fn inner_depth(&self) -> Option<usize> {
Some(self.inner_depth)
}
fn file_flags(&self) -> FileFlags {
let file = self.zip_file.borrow();
if file.is_file() {
FileFlags::File
} else if file.is_dir() {
FileFlags::Dir
} else {
FileFlags::Other
}
}
fn read_crc(&self) -> u32 {
self.zip_file.borrow().crc32()
}
fn read_sig(&self) -> Option<Signature> {
let mut file = self.zip_file.borrow_mut();
let mut buffer = self.zip_buffer.borrow_mut();
if let Ok(count) = file::read_until(file.deref_mut(), buffer.deref_mut(), 4) {
if count > 0 {
let mut data = [0; 4];
let count = cmp::min(count, 4);
data.copy_from_slice(&buffer[..count]);
return Some(data);
}
}
None
}
#[cfg(windows)]
fn read_version(&self) -> Option<String> {
if version::inner::test_extension(&self.file_path) {
let mut file = self.zip_file.borrow_mut();
let mut buffer = self.zip_buffer.borrow_mut();
if file.read_to_end(&mut buffer).is_ok() {
return version::inner::query_buffer(&mut buffer);
}
}
None
}
fn read_link(&self) -> MyResult<Option<PathBuf>> {
Ok(None)
}
fn copy_metadata(&self, _other: &dyn Entry) {
}
fn reset_metadata(&self) {
}
fn file_mode(&self) -> u32 {
let file = self.zip_file.borrow();
file.unix_mode().unwrap_or_default()
}
#[cfg(unix)]
fn owner_uid(&self) -> uid_t {
self.owner_uid
}
#[cfg(unix)]
fn owner_gid(&self) -> gid_t {
self.owner_gid
}
fn file_size(&self) -> u64 {
let file = self.zip_file.borrow();
file.size()
}
fn file_time(&self) -> SystemTime {
let file = self.zip_file.borrow();
let time = file.last_modified().unwrap_or_default();
let year = time.year() as i32;
let month = time.month() as u32;
let day = time.day() as u32;
let hour = time.hour() as u32;
let minute = time.minute() as u32;
let second = time.second() as u32;
Local
.with_ymd_and_hms(year, month, day, hour, minute, second)
.latest()
.map(SystemTime::from)
.unwrap_or(SystemTime::UNIX_EPOCH)
}
}
pub fn walk_entries<F: Fn(EntryResult)>(
zip_parent: &ZipParent,
zip_manager: &mut PasswordManager,
want_decrypt: bool,
function: &F,
) -> MyResult<()> {
let zip_path = zip_parent.path();
match create_archive(zip_path) {
Ok(mut zip_archive) => {
if want_decrypt {
let length = zip_archive.len();
for zip_index in 0..length {
walk_decrypt(zip_parent, &mut zip_archive, zip_manager, zip_index, function)?;
}
} else {
let length = zip_archive.len();
for zip_index in 0..length {
walk_plain(zip_parent, &mut zip_archive, zip_index, function);
}
}
}
Err(error) => {
function(Err(error));
}
}
Ok(())
}
fn create_archive(zip_path: &Path) -> MyResult<ZipArchive<BufReader<File>>> {
let zip_file = File::open(zip_path).map_err(|e| (e, zip_path))?;
let zip_reader = BufReader::new(zip_file);
let zip_archive = ZipArchive::new(zip_reader).map_err(|e| (e, zip_path))?;
Ok(zip_archive)
}
fn walk_plain<F: Fn(EntryResult)>(
zip_parent: &ZipParent,
zip_archive: &mut ZipArchive<BufReader<File>>,
zip_index: usize,
function: &F,
) {
let _ = match zip_archive.by_index_raw(zip_index) {
Ok(zip_file) => handle_file(zip_parent, zip_file, function),
Err(zip_error) => handle_error(zip_parent.path(), zip_error, function),
};
}
fn walk_decrypt<F: Fn(EntryResult)>(
zip_parent: &ZipParent,
zip_archive: &mut ZipArchive<BufReader<File>>,
zip_manager: &mut PasswordManager,
zip_index: usize,
function: &F,
) -> MyResult<()> {
let inner_path = concat_path(zip_parent, zip_archive, zip_index);
match zip_archive.by_index(zip_index) {
Ok(zip_file) => {
return handle_file(zip_parent, zip_file, function);
}
Err(zip_error) => {
if !password_error(&zip_error) {
return handle_error(&inner_path, zip_error, function);
}
}
}
if let Some(zip_password) = zip_manager.config() {
return match zip_archive.by_index_decrypt(zip_index, zip_password.as_bytes()) {
Ok(zip_file) => handle_file(zip_parent, zip_file, function),
Err(zip_error) => handle_error(&inner_path, zip_error, function),
}
}
for zip_password in zip_manager.passwords() {
if let Ok(zip_file) = zip_archive.by_index_decrypt(zip_index, zip_password.as_bytes()) {
return handle_file(zip_parent, zip_file, function);
}
}
loop {
let zip_password = zip_manager.prompt(&inner_path)?;
if let Ok(zip_file) = zip_archive.by_index_decrypt(zip_index, zip_password.as_bytes()) {
return handle_file(zip_parent, zip_file, function);
}
}
}
fn handle_file<F: Fn(EntryResult)>(
zip_parent: &ZipParent,
zip_file: ZipFile<BufReader<File>>,
function: &F,
) -> MyResult<()> {
let zip_entry = PkZipEntry::new(zip_parent, zip_file);
function(Ok(&zip_entry));
Ok(())
}
fn handle_error<F: Fn(EntryResult)>(
zip_path: &Path,
zip_error: ZipError,
function: &F,
) -> MyResult<()> {
function(Err(MyError::from((zip_error, zip_path))));
Ok(())
}
fn concat_path(
zip_parent: &ZipParent,
zip_archive: &mut ZipArchive<BufReader<File>>,
zip_index: usize,
) -> PathBuf {
if let Ok(zip_file) = zip_archive.by_index_raw(zip_index) {
zip_parent.path().join(zip_file.name())
} else {
zip_parent.path().to_path_buf()
}
}
fn password_error(zip_error: &ZipError) -> bool {
let regex = regex_insensitive!(r"\bpassword\b");
match zip_error {
ZipError::UnsupportedArchive(text) => regex.is_match(text),
ZipError::InvalidPassword => true,
_ => false,
}
}