mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-06 02:29:57 +02:00
Add password support to upload action
This commit is contained in:
parent
4462e1d0a6
commit
1c17ef0fcd
4 changed files with 82 additions and 7 deletions
1
IDEAS.md
1
IDEAS.md
|
@ -32,3 +32,4 @@
|
|||
- Ubuntu PPA package
|
||||
- Move API URL generator methods out of remote file class
|
||||
- Prompt if a file download password is required
|
||||
- Do not allow empty passwords (must force with `-f`)
|
||||
|
|
|
@ -22,6 +22,7 @@ use url::{
|
|||
Url,
|
||||
};
|
||||
|
||||
use crypto::b64;
|
||||
use crypto::key_set::KeySet;
|
||||
use ext::status_code::StatusCodeExt;
|
||||
use file::file::File as SendFile;
|
||||
|
@ -32,9 +33,16 @@ use reader::{
|
|||
ProgressReader,
|
||||
ProgressReporter,
|
||||
};
|
||||
use super::password::{
|
||||
Error as PasswordError,
|
||||
Password,
|
||||
};
|
||||
|
||||
type EncryptedReader = ProgressReader<BufReader<EncryptedFileReader>>;
|
||||
|
||||
/// The name of the header that is used for the authentication nonce.
|
||||
const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
|
||||
|
||||
/// A file upload action to a Send server.
|
||||
pub struct Upload {
|
||||
/// The Send host to upload the file to.
|
||||
|
@ -42,14 +50,18 @@ pub struct Upload {
|
|||
|
||||
/// The file to upload.
|
||||
path: PathBuf,
|
||||
|
||||
/// An optional password to protect the file with.
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
impl Upload {
|
||||
/// Construct a new upload action.
|
||||
pub fn new(host: Url, path: PathBuf) -> Self {
|
||||
pub fn new(host: Url, path: PathBuf, password: Option<String>) -> Self {
|
||||
Self {
|
||||
host,
|
||||
path,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,15 +94,24 @@ impl Upload {
|
|||
.start(reader_len);
|
||||
|
||||
// Execute the request
|
||||
let result = self.execute_request(req, client, &key)
|
||||
.map_err(|err| err.into());
|
||||
// TODO: don't fail on nonce error, just don't use it
|
||||
let (result, nonce) = self.execute_request(req, client, &key)?;
|
||||
|
||||
// Mark the reporter as finished
|
||||
reporter.lock()
|
||||
.map_err(|_| UploadError::Progress)?
|
||||
.finish();
|
||||
|
||||
result
|
||||
// Change the password if set
|
||||
if let Some(password) = self.password {
|
||||
Password::new(
|
||||
&result.to_download_file(),
|
||||
&password,
|
||||
nonce,
|
||||
).invoke(client)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Create a blob of encrypted metadata.
|
||||
|
@ -197,7 +218,7 @@ impl Upload {
|
|||
/// Execute the given request, and create a file object that represents the
|
||||
/// uploaded file.
|
||||
fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
||||
-> Result<SendFile, UploadError>
|
||||
-> Result<(SendFile, Option<Vec<u8>>), UploadError>
|
||||
{
|
||||
// Execute the request
|
||||
let mut response = match client.execute(req) {
|
||||
|
@ -214,6 +235,16 @@ impl Upload {
|
|||
);
|
||||
}
|
||||
|
||||
// Try to get the nonce, don't error on failure
|
||||
let nonce = response.headers()
|
||||
.get_raw(HEADER_AUTH_NONCE)
|
||||
.and_then(|h| h.one())
|
||||
.and_then(|line| String::from_utf8(line.to_vec()).ok())
|
||||
.and_then(|line| line.split_terminator(" ").skip(1).next()
|
||||
.map(|line| line.to_owned())
|
||||
)
|
||||
.and_then(|nonce| b64::decode(&nonce).ok());
|
||||
|
||||
// Decode the response
|
||||
let response: UploadResponse = match response.json() {
|
||||
Ok(response) => response,
|
||||
|
@ -221,7 +252,10 @@ impl Upload {
|
|||
};
|
||||
|
||||
// Transform the responce into a file object
|
||||
Ok(response.into_file(self.host.clone(), &key)?)
|
||||
Ok((
|
||||
response.into_file(self.host.clone(), &key)?,
|
||||
nonce,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,6 +357,10 @@ pub enum Error {
|
|||
/// An error occurred while uploading the file.
|
||||
#[fail(display = "Failed to upload the file")]
|
||||
Upload(#[cause] UploadError),
|
||||
|
||||
/// An error occurred while setting the password.
|
||||
#[fail(display = "Failed to set the password")]
|
||||
Password(#[cause] PasswordError),
|
||||
}
|
||||
|
||||
impl From<MetaError> for Error {
|
||||
|
@ -349,6 +387,12 @@ impl From<UploadError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<PasswordError> for Error {
|
||||
fn from(err: PasswordError) -> Error {
|
||||
Error::Password(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum PrepareError {
|
||||
/// Failed to prepare the file metadata for uploading.
|
||||
|
|
|
@ -39,7 +39,8 @@ impl<'a> Upload<'a> {
|
|||
let bar = Arc::new(Mutex::new(ProgressBar::new_upload()));
|
||||
|
||||
// Execute an upload action
|
||||
let file = ApiUpload::new(host, path).invoke(&client, bar)?;
|
||||
let file = ApiUpload::new(host, path, self.cmd.password())
|
||||
.invoke(&client, bar)?;
|
||||
|
||||
// Get the download URL, and report it in the console
|
||||
let url = file.download_url(true);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use ffsend_api::url::{ParseError, Url};
|
||||
|
||||
use rpassword::prompt_password_stderr;
|
||||
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
||||
|
||||
use app::SEND_DEF_HOST;
|
||||
|
@ -23,6 +24,14 @@ impl<'a: 'b, 'b> CmdUpload<'a> {
|
|||
.help("The file to upload")
|
||||
.required(true)
|
||||
.multiple(false))
|
||||
.arg(Arg::with_name("password")
|
||||
.long("password")
|
||||
.short("p")
|
||||
.alias("pass")
|
||||
.value_name("PASSWORD")
|
||||
.min_values(0)
|
||||
.max_values(1)
|
||||
.help("Protect file with a password"))
|
||||
.arg(Arg::with_name("host")
|
||||
.long("host")
|
||||
.short("h")
|
||||
|
@ -97,4 +106,24 @@ impl<'a: 'b, 'b> CmdUpload<'a> {
|
|||
pub fn copy(&self) -> bool {
|
||||
self.matches.is_present("copy")
|
||||
}
|
||||
|
||||
/// Get the password.
|
||||
pub fn password(&'a self) -> Option<String> {
|
||||
// Return none if the property was not set
|
||||
if !self.matches.is_present("password") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the password from the arguments
|
||||
if let Some(password) = self.matches.value_of("password") {
|
||||
return Some(password.into());
|
||||
}
|
||||
|
||||
// Prompt for the password
|
||||
// TODO: don't unwrap/expect
|
||||
Some(
|
||||
prompt_password_stderr("Password: ")
|
||||
.expect("failed to read password from stdin")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue