ex-cli 1.21.0

Command line tool to find, filter, sort and list files.
Documentation
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()
    }

    // noinspection DuplicatedCode
    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,
) {
    // Get entry metadata only.
    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<()> {
    // Expand entry without password.
    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);
            }
        }
    }
    // Expand entry with password from command line.
    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),
        }
    }
    // Expand entry with passwords from previous prompts.
    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);
        }
    }
    // Expand entry with passwords from new prompts.
    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,
    }
}