Make ouch support paths with dot-dot (..) for input files/directories

This commit is contained in:
Vinícius Rodrigues Miguel 2021-03-25 03:20:20 -03:00
parent b343e7872c
commit 965041310c
5 changed files with 60 additions and 50 deletions

View File

@ -1,7 +1,8 @@
use std::{convert::TryFrom, path::PathBuf, vec::Vec}; use std::{convert::TryFrom, fs, path::{Path, PathBuf}, vec::Vec};
use clap::{Arg, Values}; use clap::{Arg, Values};
// use colored::Colorize; use colored::Colorize;
use error::Error;
use crate::error; use crate::error;
use crate::extension::Extension; use crate::extension::Extension;
@ -10,11 +11,11 @@ use crate::file::File;
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub enum CommandKind { pub enum CommandKind {
Compression( Compression(
// Files to be compressed /// Files to be compressed
Vec<PathBuf>, Vec<PathBuf>,
), ),
Decompression( Decompression(
// Files to be decompressed and their extensions /// Files to be decompressed and their extensions
Vec<File>, Vec<File>,
), ),
} }
@ -66,23 +67,30 @@ pub fn get_matches() -> clap::ArgMatches<'static> {
clap_app().get_matches() clap_app().get_matches()
} }
// holy spaghetti code
impl TryFrom<clap::ArgMatches<'static>> for Command { impl TryFrom<clap::ArgMatches<'static>> for Command {
type Error = error::Error; type Error = error::Error;
fn try_from(matches: clap::ArgMatches<'static>) -> error::OuchResult<Command> { fn try_from(matches: clap::ArgMatches<'static>) -> error::OuchResult<Command> {
let process_decompressible_input = |input_files: Values| { let process_decompressible_input = |input_files: Values| {
let input_files = let input_files =
input_files.map(|filename| (filename, Extension::new(filename))); input_files.map(|filename| (Path::new(filename), Extension::new(filename)));
for file in input_files.clone() { for file in input_files.clone() {
if let (file, Err(_)) = file { match file {
return Err(error::Error::InputsMustHaveBeenDecompressible(file.into())); (filename, Ok(_)) => {
let path = Path::new(filename);
if !path.exists() {
return Err(error::Error::FileNotFound(filename.into()))
}
},
(filename, Err(_)) => {
return Err(error::Error::InputsMustHaveBeenDecompressible(filename.into()));
}
} }
} }
Ok(input_files Ok(input_files
.map(|(filename, extension)| (PathBuf::from(filename), extension.unwrap())) .map(|(filename, extension)| (fs::canonicalize(filename).unwrap(), extension.unwrap()))
.map(File::from) .map(File::from)
.collect::<Vec<_>>()) .collect::<Vec<_>>())
}; };
@ -104,13 +112,20 @@ impl TryFrom<clap::ArgMatches<'static>> for Command {
if output_is_compressible { if output_is_compressible {
// The supplied output is compressible, so we'll compress our inputs to it // The supplied output is compressible, so we'll compress our inputs to it
// println!( let canonical_paths = input_files.clone().map(Path::new).map(fs::canonicalize);
// "{}: trying to compress input files into '{}'", for (filename, canonical_path) in input_files.zip(canonical_paths.clone()) {
// "info".yellow(), if let Err(err) = canonical_path {
// output_file let path = PathBuf::from(filename);
// ); if !path.exists() {
return Err(Error::FileNotFound(path))
}
let input_files = input_files.map(PathBuf::from).collect(); eprintln!("{} {}", "[ERROR]".red(), err);
return Err(Error::IOError);
}
}
let input_files = canonical_paths.map(Result::unwrap).collect();
return Ok(Command { return Ok(Command {
kind: CommandKind::Compression(input_files), kind: CommandKind::Compression(input_files),

View File

@ -1,4 +1,4 @@
use std::{fs, path::PathBuf}; use std::{env, fs, path::PathBuf};
use colored::Colorize; use colored::Colorize;
use tar::Builder; use tar::Builder;
@ -19,13 +19,21 @@ impl TarCompressor {
} }
fn make_archive_from_files(input_filenames: Vec<PathBuf>) -> OuchResult<Vec<u8>> { fn make_archive_from_files(input_filenames: Vec<PathBuf>) -> OuchResult<Vec<u8>> {
let change_dir_and_return_parent = |filename: &PathBuf| -> OuchResult<PathBuf> {
let previous_location = env::current_dir()?;
let parent = filename.parent().unwrap();
env::set_current_dir(parent)?;
Ok(previous_location)
};
let buf = Vec::new(); let buf = Vec::new();
let mut b = Builder::new(buf); let mut b = Builder::new(buf);
for filename in input_filenames { for filename in input_filenames {
// TODO: check if file exists let previous_location = change_dir_and_return_parent(&filename)?;
// Safe unwrap since this filename came from `fs::canonicalize`.
let filename = filename.file_name().unwrap();
for entry in WalkDir::new(&filename) { for entry in WalkDir::new(&filename) {
let entry = entry?; let entry = entry?;
let path = entry.path(); let path = entry.path();
@ -34,6 +42,7 @@ impl TarCompressor {
} }
b.append_file(path, &mut fs::File::open(path)?)?; b.append_file(path, &mut fs::File::open(path)?)?;
} }
env::set_current_dir(previous_location)?;
} }
Ok(b.into_inner()?) Ok(b.into_inner()?)

View File

@ -1,4 +1,4 @@
use std::fmt; use std::{fmt, path::PathBuf};
use colored::Colorize; use colored::Colorize;
@ -10,29 +10,29 @@ pub enum Error {
InvalidUnicode, InvalidUnicode,
InvalidInput, InvalidInput,
IOError, IOError,
FileNotFound, FileNotFound(PathBuf),
AlreadyExists, AlreadyExists,
InvalidZipArchive(&'static str), InvalidZipArchive(&'static str),
PermissionDenied, PermissionDenied,
UnsupportedZipArchive(&'static str), UnsupportedZipArchive(&'static str),
InputsMustHaveBeenDecompressible(String), InputsMustHaveBeenDecompressible(PathBuf),
} }
pub type OuchResult<T> = Result<T, Error>; pub type OuchResult<T> = Result<T, Error>;
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ", "[ERROR]".red())?;
match self { match self {
Error::MissingExtensionError(filename) => { Error::MissingExtensionError(filename) => {
write!(f, "cannot compress to \'{}\', likely because it has an unsupported (or missing) extension.", filename) write!(f, "cannot compress to \'{}\', likely because it has an unsupported (or missing) extension.", filename)
}, },
Error::InputsMustHaveBeenDecompressible(file) => { Error::InputsMustHaveBeenDecompressible(file) => {
write!(f, "file '{}' is not decompressible", file.red()) write!(f, "file '{:?}' is not decompressible", file)
}, },
// TODO: find out a way to attach the missing file in question here Error::FileNotFound(file) => {
Error::FileNotFound => { // TODO: check if file == ""
write!(f, "file not found!") write!(f, "file {:?} not found!", file)
} }
_err => { _err => {
// TODO // TODO
@ -45,11 +45,11 @@ impl fmt::Display for Error {
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self { fn from(err: std::io::Error) -> Self {
match err.kind() { match err.kind() {
std::io::ErrorKind::NotFound => Self::FileNotFound, std::io::ErrorKind::NotFound => Self::FileNotFound("".into()),
std::io::ErrorKind::PermissionDenied => Self::PermissionDenied, std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
std::io::ErrorKind::AlreadyExists => Self::AlreadyExists, std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
_other => { _other => {
println!("{}: {:#?}", "IO error".red(), err); println!("{}: {}", "IO error".red(), err);
Self::IOError Self::IOError
} }
} }
@ -62,27 +62,12 @@ impl From<zip::result::ZipError> for Error {
match err { match err {
Io(io_err) => Self::from(io_err), Io(io_err) => Self::from(io_err),
InvalidArchive(filename) => Self::InvalidZipArchive(filename), InvalidArchive(filename) => Self::InvalidZipArchive(filename),
FileNotFound => Self::FileNotFound, FileNotFound => Self::FileNotFound("".into()),
UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename) UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename)
} }
} }
} }
// impl From<niffler::error::Error> for Error {
// fn from(err: niffler::error::Error) -> Self {
// use niffler::error::Error as NifErr;
// match err {
// NifErr::FeatureDisabled => {
// // Ouch is using Niffler with all its features so
// // this should be unreachable.
// unreachable!();
// },
// NifErr::FileTooShort => Self::FileTooShort,
// NifErr::IOError(io_err) => Self::from(io_err)
// }
// }
// }
impl From<walkdir::Error> for Error { impl From<walkdir::Error> for Error {
fn from(err: walkdir::Error) -> Self { fn from(err: walkdir::Error) -> Self {
eprintln!("{}: {}", "error".red(), err); eprintln!("{}: {}", "error".red(), err);

View File

@ -15,7 +15,7 @@ mod decompressors;
fn main() -> error::OuchResult<()>{ fn main() -> error::OuchResult<()>{
let print_error = |err| { let print_error = |err| {
println!("{}: {}", "error".red(), err); println!("{}", err);
}; };
let matches = cli::get_matches(); let matches = cli::get_matches();
match cli::Command::try_from(matches) { match cli::Command::try_from(matches) {

View File

@ -8,7 +8,8 @@ where
P: AsRef<Path> + 'a { P: AsRef<Path> + 'a {
let exists = path.as_ref().exists(); let exists = path.as_ref().exists();
if !exists { if !exists {
eprintln!("{}: could not find file {:?}", "error".red(), path.as_ref()); eprintln!("{}: could not find file {:?}", "[ERROR]".red(), path.as_ref());
return Err(Error::FileNotFound(PathBuf::from(path.as_ref())));
} }
Ok(()) Ok(())
} }
@ -16,7 +17,7 @@ where
pub (crate) fn check_for_multiple_files(files: &Vec<PathBuf>, format: &CompressionFormat) -> OuchResult<()> { pub (crate) fn check_for_multiple_files(files: &Vec<PathBuf>, format: &CompressionFormat) -> OuchResult<()> {
if files.len() != 1 { if files.len() != 1 {
eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "error".red(), format, format); eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "[ERROR]".red(), format, format);
return Err(Error::InvalidInput); return Err(Error::InvalidInput);
} }
@ -27,13 +28,13 @@ pub (crate) fn create_path_if_non_existent(path: &Path) -> OuchResult<()> {
if !path.exists() { if !path.exists() {
println!( println!(
"{}: attempting to create folder {:?}.", "{}: attempting to create folder {:?}.",
"info".yellow(), "[INFO]".yellow(),
&path &path
); );
std::fs::create_dir_all(path)?; std::fs::create_dir_all(path)?;
println!( println!(
"{}: directory {:#?} created.", "{}: directory {:#?} created.",
"info".yellow(), "[INFO]".yellow(),
fs::canonicalize(&path)? fs::canonicalize(&path)?
); );
} }