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. /// An optional password to decrypt a protected file.
password: Option<String>, password: Option<String>,
/// Check whether the file exists (recommended).
check_exists: bool,
} }
impl<'a> Download<'a> { impl<'a> Download<'a> {
/// Construct a new download action for the given remote file. /// 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( pub fn new(
file: &'a RemoteFile, file: &'a RemoteFile,
target: PathBuf, target: PathBuf,
password: Option<String>, password: Option<String>,
check_exists: bool,
) -> Self { ) -> Self {
Self { Self {
file, file,
target, target,
password, password,
check_exists,
} }
} }
@ -59,23 +66,18 @@ impl<'a> Download<'a> {
reporter: Arc<Mutex<ProgressReporter>>, reporter: Arc<Mutex<ProgressReporter>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Make sure the given file exists // Make sure the given file exists
let exist_response = ExistsAction::new(&self.file) if self.check_exists {
.invoke(&client)?; let exist_response = ExistsAction::new(&self.file)
.invoke(&client)?;
// Return an error if the file does not exist // Return an error if the file does not exist
if !exist_response.exists() { if !exist_response.exists() {
return Err(Error::Expired); return Err(Error::Expired);
} }
// Make sure a password is given when it is required // Make sure a password is given when it is required
let has_password = self.password.is_some(); if !self.password.is_some() && exist_response.has_password() {
if has_password != exist_response.has_password() { return Err(Error::PasswordRequired);
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");
} }
} }
@ -267,6 +269,10 @@ pub enum Error {
#[fail(display = "Failed to fetch file metadata")] #[fail(display = "Failed to fetch file metadata")]
Meta(#[cause] MetadataError), 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. /// The given Send file has expired, or did never exist in the first place.
/// Therefore the file could not be downloaded. /// Therefore the file could not be downloaded.
#[fail(display = "The file has expired or did never exist")] #[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 clap::ArgMatches;
use ffsend_api::action::download::Download as ApiDownload; 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::file::remote_file::RemoteFile;
use ffsend_api::reqwest::Client; use ffsend_api::reqwest::Client;
@ -11,6 +12,7 @@ use cmd::matcher::{
}; };
use error::ActionError; use error::ActionError;
use progress::ProgressBar; use progress::ProgressBar;
use util::prompt_password;
/// A file download action. /// A file download action.
pub struct Download<'a> { pub struct Download<'a> {
@ -41,8 +43,21 @@ impl<'a> Download<'a> {
// TODO: handle error here // TODO: handle error here
let file = RemoteFile::parse_url(url, None)?; 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 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 // Create a progress bar reporter
let bar = Arc::new(Mutex::new(ProgressBar::new_download())); let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
@ -51,7 +66,8 @@ impl<'a> Download<'a> {
ApiDownload::new( ApiDownload::new(
&file, &file,
target, target,
matcher_download.password(), password,
false,
).invoke(&client, bar)?; ).invoke(&client, bar)?;
// TODO: open the file, or it's location // TODO: open the file, or it's location

View file

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

View file

@ -14,6 +14,7 @@ use self::clipboard::{ClipboardContext, ClipboardProvider};
use self::colored::*; use self::colored::*;
use failure::{self, Fail}; use failure::{self, Fail};
use ffsend_api::url::Url; use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
/// Print a success message. /// Print a success message.
pub fn print_success(msg: &str) { pub fn print_success(msg: &str) {
@ -24,8 +25,7 @@ pub fn print_success(msg: &str) {
/// with it's causes. /// with it's causes.
pub fn print_error<E: Fail>(err: E) { pub fn print_error<E: Fail>(err: E) {
// Report each printable error, count them // Report each printable error, count them
let count = err.causes() let count = err.causes() .map(|err| format!("{}", err))
.map(|err| format!("{}", err))
.filter(|err| !err.is_empty()) .filter(|err| !err.is_empty())
.enumerate() .enumerate()
.map(|(i, err)| if i == 0 { .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()?; let mut context: ClipboardContext = ClipboardProvider::new()?;
context.set_contents(content) 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"
)),
}
}