Nicely handle and prompt passwords for downloads

This commit is contained in:
timvisee 2018-04-10 18:39:26 +02:00
parent 2719bb4834
commit 35c376ab82
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
4 changed files with 53 additions and 26 deletions

View file

@ -36,19 +36,26 @@ pub struct Download<'a> {
/// An optional password to decrypt a protected file.
password: Option<String>,
/// Check whether the file exists (recommended).
check_exists: bool,
}
impl<'a> Download<'a> {
/// Construct a new download action for the given remote file.
/// It is recommended to check whether the file exists,
/// unless that is already done.
pub fn new(
file: &'a RemoteFile,
target: PathBuf,
password: Option<String>,
check_exists: bool,
) -> Self {
Self {
file,
target,
password,
check_exists,
}
}
@ -59,23 +66,18 @@ impl<'a> Download<'a> {
reporter: Arc<Mutex<ProgressReporter>>,
) -> Result<(), Error> {
// Make sure the given file exists
let exist_response = ExistsAction::new(&self.file)
.invoke(&client)?;
if self.check_exists {
let exist_response = ExistsAction::new(&self.file)
.invoke(&client)?;
// Return an error if the file does not exist
if !exist_response.exists() {
return Err(Error::Expired);
}
// Return an error if the file does not exist
if !exist_response.exists() {
return Err(Error::Expired);
}
// Make sure a password is given when it is required
let has_password = self.password.is_some();
if has_password != exist_response.has_password() {
if has_password {
// TODO: show a proper message here
println!("file not password protected, ignoring password");
} else {
// TODO: show a propper error here, or prompt for the password
panic!("password required");
// Make sure a password is given when it is required
if !self.password.is_some() && exist_response.has_password() {
return Err(Error::PasswordRequired);
}
}
@ -267,6 +269,10 @@ pub enum Error {
#[fail(display = "Failed to fetch file metadata")]
Meta(#[cause] MetadataError),
/// A password is required, but was not given.
#[fail(display = "Missing password, password required")]
PasswordRequired,
/// The given Send file has expired, or did never exist in the first place.
/// Therefore the file could not be downloaded.
#[fail(display = "The file has expired or did never exist")]

View file

@ -2,6 +2,7 @@ use std::sync::{Arc, Mutex};
use clap::ArgMatches;
use ffsend_api::action::download::Download as ApiDownload;
use ffsend_api::action::exists::Exists as ApiExists;
use ffsend_api::file::remote_file::RemoteFile;
use ffsend_api::reqwest::Client;
@ -11,6 +12,7 @@ use cmd::matcher::{
};
use error::ActionError;
use progress::ProgressBar;
use util::prompt_password;
/// A file download action.
pub struct Download<'a> {
@ -41,8 +43,21 @@ impl<'a> Download<'a> {
// TODO: handle error here
let file = RemoteFile::parse_url(url, None)?;
// Get the target file or directory
// Get the target file or directory, and the password
let target = matcher_download.output();
let mut password = matcher_download.password();
// Check whether the file requires a password
let exists = ApiExists::new(&file).invoke(&client).unwrap();
if exists.has_password() != password.is_some() {
if exists.has_password() {
println!("This file is protected with a password.");
password = Some(prompt_password());
} else {
println!("Ignoring password, it is not required");
password = None;
}
}
// Create a progress bar reporter
let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
@ -51,7 +66,8 @@ impl<'a> Download<'a> {
ApiDownload::new(
&file,
target,
matcher_download.password(),
password,
false,
).invoke(&client, bar)?;
// TODO: open the file, or it's location

View file

@ -1,7 +1,7 @@
use clap::{Arg, ArgMatches};
use rpassword::prompt_password_stderr;
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use util::prompt_password;
/// The password argument.
pub struct ArgPassword { }
@ -41,11 +41,6 @@ impl<'a> CmdArgOption<'a> for ArgPassword {
}
// Prompt for the password
// TODO: don't unwrap/expect
// TODO: create utility function for this
Some(
prompt_password_stderr("Password: ")
.expect("failed to read password from stdin")
)
Some(prompt_password())
}
}

View file

@ -14,6 +14,7 @@ use self::clipboard::{ClipboardContext, ClipboardProvider};
use self::colored::*;
use failure::{self, Fail};
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
/// Print a success message.
pub fn print_success(msg: &str) {
@ -24,8 +25,7 @@ pub fn print_success(msg: &str) {
/// with it's causes.
pub fn print_error<E: Fail>(err: E) {
// Report each printable error, count them
let count = err.causes()
.map(|err| format!("{}", err))
let count = err.causes() .map(|err| format!("{}", err))
.filter(|err| !err.is_empty())
.enumerate()
.map(|(i, err)| if i == 0 {
@ -82,3 +82,13 @@ pub fn set_clipboard(content: String) -> Result<(), Box<StdError>> {
let mut context: ClipboardContext = ClipboardProvider::new()?;
context.set_contents(content)
}
/// Prompt the user to enter a password.
pub fn prompt_password() -> String {
match prompt_password_stderr("Password: ") {
Ok(password) => password,
Err(err) => quit_error(err.context(
"Failed to read password from stdin with password prompt"
)),
}
}