浏览代码

Nicely handle and prompt passwords for downloads

timvisee 7 年之前
父节点
当前提交
35c376ab82
共有 4 个文件被更改,包括 53 次插入26 次删除
  1. 21 15
      api/src/action/download.rs
  2. 18 2
      cli/src/action/download.rs
  3. 2 7
      cli/src/cmd/arg/password.rs
  4. 12 2
      cli/src/util.rs

+ 21 - 15
api/src/action/download.rs

@@ -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")]

+ 18 - 2
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

+ 2 - 7
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())
     }
 }

+ 12 - 2
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<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"
+        )),
+    }
+}