mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-06 10:39: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
|
- Ubuntu PPA package
|
||||||
- Move API URL generator methods out of remote file class
|
- Move API URL generator methods out of remote file class
|
||||||
- Prompt if a file download password is required
|
- Prompt if a file download password is required
|
||||||
|
- Do not allow empty passwords (must force with `-f`)
|
||||||
|
|
|
@ -22,6 +22,7 @@ use url::{
|
||||||
Url,
|
Url,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crypto::b64;
|
||||||
use crypto::key_set::KeySet;
|
use crypto::key_set::KeySet;
|
||||||
use ext::status_code::StatusCodeExt;
|
use ext::status_code::StatusCodeExt;
|
||||||
use file::file::File as SendFile;
|
use file::file::File as SendFile;
|
||||||
|
@ -32,9 +33,16 @@ use reader::{
|
||||||
ProgressReader,
|
ProgressReader,
|
||||||
ProgressReporter,
|
ProgressReporter,
|
||||||
};
|
};
|
||||||
|
use super::password::{
|
||||||
|
Error as PasswordError,
|
||||||
|
Password,
|
||||||
|
};
|
||||||
|
|
||||||
type EncryptedReader = ProgressReader<BufReader<EncryptedFileReader>>;
|
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.
|
/// A file upload action to a Send server.
|
||||||
pub struct Upload {
|
pub struct Upload {
|
||||||
/// The Send host to upload the file to.
|
/// The Send host to upload the file to.
|
||||||
|
@ -42,14 +50,18 @@ pub struct Upload {
|
||||||
|
|
||||||
/// The file to upload.
|
/// The file to upload.
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// An optional password to protect the file with.
|
||||||
|
password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Upload {
|
impl Upload {
|
||||||
/// Construct a new upload action.
|
/// 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 {
|
Self {
|
||||||
host,
|
host,
|
||||||
path,
|
path,
|
||||||
|
password,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,15 +94,24 @@ impl Upload {
|
||||||
.start(reader_len);
|
.start(reader_len);
|
||||||
|
|
||||||
// Execute the request
|
// Execute the request
|
||||||
let result = self.execute_request(req, client, &key)
|
// TODO: don't fail on nonce error, just don't use it
|
||||||
.map_err(|err| err.into());
|
let (result, nonce) = self.execute_request(req, client, &key)?;
|
||||||
|
|
||||||
// Mark the reporter as finished
|
// Mark the reporter as finished
|
||||||
reporter.lock()
|
reporter.lock()
|
||||||
.map_err(|_| UploadError::Progress)?
|
.map_err(|_| UploadError::Progress)?
|
||||||
.finish();
|
.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.
|
/// Create a blob of encrypted metadata.
|
||||||
|
@ -197,7 +218,7 @@ impl Upload {
|
||||||
/// Execute the given request, and create a file object that represents the
|
/// Execute the given request, and create a file object that represents the
|
||||||
/// uploaded file.
|
/// uploaded file.
|
||||||
fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
||||||
-> Result<SendFile, UploadError>
|
-> Result<(SendFile, Option<Vec<u8>>), UploadError>
|
||||||
{
|
{
|
||||||
// Execute the request
|
// Execute the request
|
||||||
let mut response = match client.execute(req) {
|
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
|
// Decode the response
|
||||||
let response: UploadResponse = match response.json() {
|
let response: UploadResponse = match response.json() {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
|
@ -221,7 +252,10 @@ impl Upload {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transform the responce into a file object
|
// 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.
|
/// An error occurred while uploading the file.
|
||||||
#[fail(display = "Failed to upload the file")]
|
#[fail(display = "Failed to upload the file")]
|
||||||
Upload(#[cause] UploadError),
|
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 {
|
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)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum PrepareError {
|
pub enum PrepareError {
|
||||||
/// Failed to prepare the file metadata for uploading.
|
/// 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()));
|
let bar = Arc::new(Mutex::new(ProgressBar::new_upload()));
|
||||||
|
|
||||||
// Execute an upload action
|
// 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
|
// Get the download URL, and report it in the console
|
||||||
let url = file.download_url(true);
|
let url = file.download_url(true);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use ffsend_api::url::{ParseError, Url};
|
use ffsend_api::url::{ParseError, Url};
|
||||||
|
|
||||||
|
use rpassword::prompt_password_stderr;
|
||||||
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use app::SEND_DEF_HOST;
|
use app::SEND_DEF_HOST;
|
||||||
|
@ -23,6 +24,14 @@ impl<'a: 'b, 'b> CmdUpload<'a> {
|
||||||
.help("The file to upload")
|
.help("The file to upload")
|
||||||
.required(true)
|
.required(true)
|
||||||
.multiple(false))
|
.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")
|
.arg(Arg::with_name("host")
|
||||||
.long("host")
|
.long("host")
|
||||||
.short("h")
|
.short("h")
|
||||||
|
@ -97,4 +106,24 @@ impl<'a: 'b, 'b> CmdUpload<'a> {
|
||||||
pub fn copy(&self) -> bool {
|
pub fn copy(&self) -> bool {
|
||||||
self.matches.is_present("copy")
|
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