diff --git a/api/src/action/download.rs b/api/src/action/download.rs index 4085db0..04fff14 100644 --- a/api/src/action/download.rs +++ b/api/src/action/download.rs @@ -36,19 +36,26 @@ pub struct Download<'a> { /// An optional password to decrypt a protected file. password: Option, + + /// 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, + check_exists: bool, ) -> Self { Self { file, target, password, + check_exists, } } @@ -59,23 +66,18 @@ impl<'a> Download<'a> { reporter: Arc>, ) -> 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")] diff --git a/cli/src/action/download.rs b/cli/src/action/download.rs index 4403995..62e7c47 100644 --- a/cli/src/action/download.rs +++ b/cli/src/action/download.rs @@ -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 diff --git a/cli/src/cmd/arg/password.rs b/cli/src/cmd/arg/password.rs index ea58bfd..6d8b16a 100644 --- a/cli/src/cmd/arg/password.rs +++ b/cli/src/cmd/arg/password.rs @@ -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()) } } diff --git a/cli/src/util.rs b/cli/src/util.rs index 100064c..fcb466f 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -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(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> { 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" + )), + } +}