use crate::{decrypt_file, create_target_path, save_decrypted};
use std::path::{PathBuf, Path};
use clap::{App, Arg, ArgMatches};
use std::io;
use crate::error::Error;
use crate::decrypt::PlainContent;
use std::process::exit;
const FILE_PARAM: &str = "file";
const PASSPHRASE_PARAM: &str = "passphrase";
const OVERWRITE_PARAM: &str = "overwrite";
const NO_OVERWRITE_PARAM: &str = "no-overwrite";
pub fn setup_args() -> ArgMatches<'static> {
App::new("axxd")
.version("0.1.0")
.about("Axxd - an [axx] file [d]ecryptor")
.arg(
Arg::with_name(FILE_PARAM)
.short("f")
.long("file")
.value_name("FILE")
.help("Input file path")
.takes_value(true),
)
.arg(
Arg::with_name(PASSPHRASE_PARAM)
.short("p")
.long("passphrase")
.value_name("PASS")
.help("Encryption passphrase")
.takes_value(true),
)
.arg(
Arg::with_name(OVERWRITE_PARAM)
.short("o")
.long("overwrite")
.help("Overwrite target file if exists")
.takes_value(false)
.conflicts_with(NO_OVERWRITE_PARAM),
)
.arg(
Arg::with_name(NO_OVERWRITE_PARAM)
.short("n")
.long("no-overwrite")
.help("Abort when target file already exists")
.takes_value(false)
.conflicts_with(OVERWRITE_PARAM),
)
.get_matches()
}
pub fn cli_decrypt(args: ArgMatches) {
let overwrite = should_overwrite(&args);
let (filename, pass) = retrieve_params(args);
println!("Decrypting {}...", filename);
let source_file = PathBuf::from(filename);
match decrypt_file(&source_file, &pass) {
Ok(content) => {
prompt_save_file_cli(source_file, content, overwrite);
}
Err(e) => {
println!("Cannot decrypt file.");
display_error_and_quit(e);
}
}
}
fn should_overwrite(args: &ArgMatches) -> Option<bool> {
let overwrite = args.is_present(OVERWRITE_PARAM);
let no_overwrite = args.is_present(NO_OVERWRITE_PARAM);
if overwrite || no_overwrite {
Some(overwrite)
} else {
None
}
}
fn retrieve_params(args: ArgMatches) -> (String, String) {
let filename = get_param_or_prompt(&args, FILE_PARAM, "Enter file path: ", prompt_plain_text);
let pass = get_param_or_prompt(&args, PASSPHRASE_PARAM, "Enter passphrase: ", prompt_pass);
(filename, pass)
}
fn get_param_or_prompt<F>(args: &ArgMatches, param: &str, message: &str, prompt: F) -> String
where F: Fn(&str) -> Option<String> {
let value = match args.value_of(param) {
Some(value) => value.to_string(),
None => prompt(message).unwrap()
};
value.trim_end_matches('\n')
.trim_end_matches('\r')
.to_string()
}
fn prompt_plain_text(prompt: &str) -> Option<String> {
println!("{}", prompt);
let mut value: String = String::new();
if io::stdin().read_line(&mut value).is_ok() {
Some(value)
} else {
None
}
}
fn prompt_pass(prompt: &str) -> Option<String> {
rpassword::prompt_password_stdout(prompt).ok()
}
fn display_error_and_quit(e: Error) {
match e {
Error::FileNameEncoding(e) => {
println!("Passphrase may be incorrect or file is is corrupted. \nDetails: {:?}", e);
},
Error::Io(e) => {
println!("Cannot read/write the file. \nDetails: {:?}", e);
}
Error::Cipher(e) => {
println!("Decryption error, passphrase is incorrect or data is corrupted. \nDetails {:?}", e);
}
Error::MissingHeader(e) => {
println!("Encrypted file is in incorrect format. Missing metadata that is needed to decrypt it. \nDetails: {:?}", e);
}
Error::MalformedContent { description, content } => {
println!("Cannot read metadata from file. \n{}, encountered on {:?}", description, content);
}
}
exit(254);
}
fn prompt_save_file_cli<P: AsRef<Path>>(source_file: P, content: PlainContent, overwrite: Option<bool>) {
let target_path = create_target_path(&source_file, &content);
println!("File successfully decrypted. Saving into {:?}.", target_path);
if target_path.exists() {
print!("WARNING: File already exits. ");
let overwrite = if let Some(param_value) = overwrite {
param_value
} else {
ask("Proceed? [y/n]")
};
if overwrite {
println!("Overwriting {:?}.", target_path);
} else {
println!("Aborting.");
exit(255);
}
}
if let Err(e) = save_decrypted(content, target_path) {
display_error_and_quit(e);
}
}
fn ask(question: &str) -> bool {
println!("{}", question);
let mut answer: String = String::new();
io::stdin().read_line(&mut answer).unwrap();
answer.to_lowercase().starts_with('y')
}