diff --git a/IDEAS.md b/IDEAS.md index d7bdf04..0fb5ab0 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -1,7 +1,5 @@ # Ideas - Endpoints: - - exists - - metadata - delete - allow creating non existent directories with the `-f` flag - only allow file extension renaming on upload with `-f` flag diff --git a/api/src/action/download.rs b/api/src/action/download.rs index 12b88f1..e1c9ddb 100644 --- a/api/src/action/download.rs +++ b/api/src/action/download.rs @@ -18,6 +18,10 @@ use crypto::sig::signature_encoded; use ext::status_code::StatusCodeExt; use file::remote_file::RemoteFile; use reader::{EncryptedFileWriter, ProgressReporter, ProgressWriter}; +use super::exists::{ + Error as ExistsError, + Exists as ExistsAction, +}; use super::metadata::{ Error as MetadataError, Metadata as MetadataAction, @@ -55,6 +59,27 @@ impl<'a> Download<'a> { client: &Client, reporter: Arc>, ) -> Result<(), Error> { + // Make sure the given file 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); + } + + // 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"); + } + } + // Create a key set for the file let mut key = KeySet::from(self.file, self.password.as_ref()); @@ -232,6 +257,11 @@ impl<'a> Download<'a> { #[derive(Fail, Debug)] pub enum Error { + /// An error occurred while checking whether the file exists on the + /// server. + #[fail(display = "Failed to check whether the file exists")] + Exists(#[cause] ExistsError), + /// An error occurred while fetching the metadata of the file. /// This step is required in order to succsessfully decrypt the /// file that will be downloaded. @@ -257,6 +287,12 @@ pub enum Error { File(String, #[cause] FileError), } +impl From for Error { + fn from(err: ExistsError) -> Error { + Error::Exists(err) + } +} + impl From for Error { fn from(err: MetadataError) -> Error { Error::Meta(err) diff --git a/api/src/action/exists.rs b/api/src/action/exists.rs index 8f24975..2e352c5 100644 --- a/api/src/action/exists.rs +++ b/api/src/action/exists.rs @@ -49,8 +49,9 @@ impl<'a> Exists<'a> { } // Parse the response - let response = response.json::() + let mut response = response.json::() .map_err(|_| Error::Malformed)?; + response.set_exists(true); // TODO: fetch the metadata nonce from the response headers @@ -84,6 +85,11 @@ impl ExistsResponse { self.exists } + /// Set whether the remote file exists. + pub fn set_exists(&mut self, exists: bool) { + self.exists = exists; + } + /// Whether the remote file is protected by a password. pub fn has_password(&self) -> bool { self.has_password diff --git a/api/src/file/metadata.rs b/api/src/file/metadata.rs index 7903c6e..4f3bcb5 100644 --- a/api/src/file/metadata.rs +++ b/api/src/file/metadata.rs @@ -52,6 +52,11 @@ impl Metadata { &self.name } + /// Get the file MIME type. + pub fn mime(&self) -> &str { + &self.mime + } + /// Get the input vector // TODO: use an input vector length from a constant pub fn iv(&self) -> [u8; 12] { diff --git a/api/src/file/remote_file.rs b/api/src/file/remote_file.rs index f6662c7..ac4631b 100644 --- a/api/src/file/remote_file.rs +++ b/api/src/file/remote_file.rs @@ -133,6 +133,11 @@ impl RemoteFile { )) } + /// Get the file ID. + pub fn id(&self) -> &str { + &self.id + } + /// Get the raw secret. pub fn secret_raw(&self) -> &Vec { // A secret must have been set diff --git a/cli/src/action/info.rs b/cli/src/action/info.rs index d900ddf..6cec1ad 100644 --- a/cli/src/action/info.rs +++ b/cli/src/action/info.rs @@ -1,9 +1,21 @@ -use ffsend_api::action::info::Info as ApiInfo; -use ffsend_api::file::remote_file::RemoteFile; +use failure::Fail; +use ffsend_api::action::exists::{ + Error as ExistsError, + Exists as ApiExists, +}; +use ffsend_api::action::info::{ + Error as InfoError, + Info as ApiInfo, +}; +use ffsend_api::action::metadata::Metadata as ApiMetadata; +use ffsend_api::file::remote_file::{ + FileParseError, + RemoteFile, +}; use ffsend_api::reqwest::Client; use cmd::cmd_info::CmdInfo; -use error::ActionError; +use util::print_error; /// A file info action. pub struct Info<'a> { @@ -20,7 +32,7 @@ impl<'a> Info<'a> { /// Invoke the info action. // TODO: create a trait for this method - pub fn invoke(&self) -> Result<(), ActionError> { + pub fn invoke(&self) -> Result<(), Error> { // Get the share URL let url = self.cmd.url(); @@ -33,13 +45,76 @@ impl<'a> Info<'a> { // TODO: show an informative error if the owner token isn't set - // Execute the info fetch action + // Make sure the file exists + let exists_response = ApiExists::new(&file) + .invoke(&client)?; + + // Make sure the file exists + if !exists_response.exists() { + return Err(Error::Expired); + } + + // TODO: make sure a password is set if required + + // Fetch both file info and metadata let info = ApiInfo::new(&file, None).invoke(&client)?; + // TODO: supply a password here + let metadata = ApiMetadata::new(&file, None).invoke(&client) + .map_err(|err| print_error(err.context( + "Failed to fetch file metadata, showing limited info", + ))) + .ok(); // Print the result + println!("ID: {}", file.id()); + if let Some(metadata) = metadata { + println!("File name: {}", metadata.metadata().name()); + println!("MIME type: {}", metadata.metadata().mime()); + } println!("Downloads: {} of {}", info.download_count(), info.download_limit()); - println!("TTL: {}", info.ttl_millis()); + println!("TTL: {} ms", info.ttl_millis()); + + // TODO: show the file size, fetch TTL from metadata? Ok(()) } } + +#[derive(Debug, Fail)] +pub enum Error { + /// Failed to parse a share URL, it was invalid. + /// This error is not related to a specific action. + #[fail(display = "Invalid share URL")] + InvalidUrl(#[cause] FileParseError), + + /// An error occurred while checking if the file exists. + #[fail(display = "Failed to check whether the file exists")] + Exists(#[cause] ExistsError), + + /// An error occurred while fetching the file information. + #[fail(display = "Failed to fetch file info")] + Info(#[cause] InfoError), + + /// The given Send file has expired, or did never exist in the first place. + // TODO: do not return an error, but write to stdout that the file does not exist + #[fail(display = "The file has expired or did never exist")] + Expired, +} + +impl From for Error { + fn from(err: FileParseError) -> Error { + Error::InvalidUrl(err) + } +} + +impl From for Error { + fn from(err: ExistsError) -> Error { + Error::Exists(err) + } +} + +impl From for Error { + fn from(err: InfoError) -> Error { + Error::Info(err) + } +} diff --git a/cli/src/error.rs b/cli/src/error.rs index a2a3e39..568a013 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -1,10 +1,11 @@ use ffsend_api::action::download::Error as DownloadError; -use ffsend_api::action::info::Error as InfoError; use ffsend_api::action::params::Error as ParamsError; use ffsend_api::action::password::Error as PasswordError; use ffsend_api::action::upload::Error as UploadError; use ffsend_api::file::remote_file::FileParseError; +use action::info::Error as InfoError; + #[derive(Fail, Debug)] pub enum Error { /// An error occurred while invoking an action. @@ -12,6 +13,12 @@ pub enum Error { Action(#[cause] ActionError), } +impl From for Error { + fn from(err: InfoError) -> Error { + Error::Action(ActionError::Info(err)) + } +} + impl From for Error { fn from(err: ActionError) -> Error { Error::Action(err)