mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-06 02:29:57 +02:00
Nicely handle and prompt passwords for downloads
This commit is contained in:
parent
2719bb4834
commit
35c376ab82
4 changed files with 53 additions and 26 deletions
|
@ -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")]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue