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::util::file;
#[cfg(windows)]
use crate::util::version;
use crate::zip::manager::PasswordManager;
use crate::zip::parent::ZipParent;
use path_clean::PathClean;
use sevenz_rust2::{ArchiveEntry, ArchiveReader, Error, Password};
use std::cell::RefCell;
use std::cmp;
use std::ffi::OsStr;
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};

struct SevenZEntry<'a> {
    zip_entry: &'a ArchiveEntry,
    zip_reader: RefCell<&'a mut dyn Read>,
    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> SevenZEntry<'a> {
    fn new(
        zip_parent: &ZipParent,
        zip_entry: &'a ArchiveEntry,
        zip_reader: &'a mut dyn Read,
    ) -> Self {
        let (file_path, file_depth, inner_path, inner_depth) = Self::prepare_path(zip_parent, zip_entry);
        let zip_reader = RefCell::new(zip_reader);
        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_entry,
            zip_reader,
            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_entry: &ArchiveEntry,
    ) -> (PathBuf, usize, PathBuf, usize) {
        let inner_path = PathBuf::from(&zip_entry.name).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 SevenZEntry<'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 {
        if self.zip_entry.is_directory {
            FileFlags::Dir
        } else {
            FileFlags::File
        }
    }

    fn read_crc(&self) -> u32 {
        self.zip_entry.crc as u32
    }

    // noinspection DuplicatedCode
    fn read_sig(&self) -> Option<Signature> {
        let mut reader = self.zip_reader.borrow_mut();
        let mut buffer = self.zip_buffer.borrow_mut();
        if let Ok(count) = file::read_until(reader.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 reader = self.zip_reader.borrow_mut();
            let mut buffer = self.zip_buffer.borrow_mut();
            if reader.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 {
        0
    }

    #[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 {
        self.zip_entry.size
    }

    fn file_time(&self) -> SystemTime {
        SystemTime::from(self.zip_entry.last_modified_date)
    }
}

pub fn walk_entries<F: Fn(EntryResult)>(
    zip_parent: &ZipParent,
    zip_manager: &mut PasswordManager,
    want_decrypt: bool,
    function: &F,
) -> MyResult<()> {
    // Expand entry without password.
    let zip_path = zip_parent.path();
    let zip_password = default_password(want_decrypt);
    match walk_reader(zip_parent, zip_password, function) {
        Ok(()) => {
            return Ok(());
        }
        Err(zip_error) => {
            if !password_error(&zip_error, want_decrypt) {
                function(Err(MyError::from((zip_error, zip_path))));
                return Ok(());
            }
        }
    }
    // Expand entry with password from command line.
    if let Some(zip_password) = zip_manager.config() {
        let zip_password = Password::from(zip_password.as_str());
        let _ = walk_reader(zip_parent, zip_password, function);
        return Ok(());
    }
    // Expand entry with passwords from previous prompts.
    for zip_password in zip_manager.passwords() {
        let zip_password = Password::from(zip_password.as_str());
        if walk_reader(zip_parent, zip_password, function).is_ok() {
            return Ok(());
        }
    }
    // Expand entry with passwords from new prompts.
    loop {
        let zip_password = zip_manager.prompt(zip_path)?;
        let zip_password = Password::from(zip_password.as_str());
        if walk_reader(zip_parent, zip_password, function).is_ok() {
            return Ok(());
        }
    }
}

fn walk_reader<F: Fn(EntryResult)>(
    zip_parent: &ZipParent,
    zip_password: Password,
    function: &F,
) -> Result<(), Error> {
    let zip_path = zip_parent.path();
    let mut zip_reader = ArchiveReader::open(zip_path, zip_password)?;
    zip_reader.for_each_entries(|zip_entry, zip_reader| {
        let zip_entry = SevenZEntry::new(zip_parent, zip_entry, zip_reader);
        function(Ok(&zip_entry));
        Ok(true)
    })
}

fn default_password(want_decrypt: bool) -> Password {
    if want_decrypt {
        Password::empty()
    } else {
        Password::from("x")
    }
}

fn password_error(zip_error: &Error, want_decrypt: bool) -> bool {
    match zip_error {
        Error::PasswordRequired => want_decrypt,
        Error::MaybeBadPassword(_) => want_decrypt,
        _ => false,
    }
}