diff --git a/ROADMAP.md b/ROADMAP.md index 47d571d..137ba62 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,11 +1,11 @@ # Release 0.1 +- Sort history entries +- Panic when secret is missing from URL with info action - History compiler flag - Lowercase error messages -- Automatically get owner token, from file history when setting password - Switch to `directories` instead of `app_dirs2`? - Allow file/directory archiving on upload - Allow unarchiving on download -- Show a simplified command list when calling `ffsend` without arguments - Use clipboard through `xclip` on Linux if available for persistence - Allow environment variable settings using `Arg.env(NAME)` - Automated releases through CI @@ -13,7 +13,12 @@ - Ubuntu PPA package - Implement error handling everywhere properly - Embed request errors +- Extract utility module +- Think of a new description: + Securely and easily share files from the command line; + a fully featured Firefox Send client. - Check all TODOs, solve them when possible +- Windows, macOS and Redox support # Future releases - Color usage flag diff --git a/api/src/file/remote_file.rs b/api/src/file/remote_file.rs index 56bdb04..7236351 100644 --- a/api/src/file/remote_file.rs +++ b/api/src/file/remote_file.rs @@ -252,11 +252,20 @@ impl RemoteFile { &mut self.owner_token } - /// Set the owner token. + /// Set the owner token, wrapped in an option. + /// If `None` is given, the owner token will be unset. pub fn set_owner_token(&mut self, token: Option) { self.owner_token = token; } + /// Check whether an owner token is set in this remote file. + pub fn has_owner_token(&self) -> bool { + self.owner_token + .clone() + .map(|t| !t.is_empty()) + .unwrap_or(false) + } + /// Get the host URL for this remote file. pub fn host(&self) -> Url { self.host.clone() diff --git a/cli/src/action/delete.rs b/cli/src/action/delete.rs index e9125da..b8c1d82 100644 --- a/cli/src/action/delete.rs +++ b/cli/src/action/delete.rs @@ -44,8 +44,9 @@ impl<'a> Delete<'a> { // Create a reqwest client let client = Client::new(); - // Parse the remote file based on the share URL, get the owner token + // Parse the remote file based on the share URL, derive the owner token from history let mut file = RemoteFile::parse_url(url, matcher_delete.owner())?; + history_tool::derive_owner_token(&matcher_main, &mut file); // Ensure the owner token is set ensure_owner_token(file.owner_token_mut(), &matcher_main); diff --git a/cli/src/action/info.rs b/cli/src/action/info.rs index 3a4df27..07d0f06 100644 --- a/cli/src/action/info.rs +++ b/cli/src/action/info.rs @@ -50,9 +50,9 @@ impl<'a> Info<'a> { // Create a reqwest client let client = Client::new(); - // Parse the remote file based on the share URL, get the password + // Parse the remote file based on the share URL, derive the owner token from history let mut file = RemoteFile::parse_url(url, matcher_info.owner())?; - let mut password = matcher_info.password(); + history_tool::derive_owner_token(&matcher_main, &mut file); // Ensure the owner token is set ensure_owner_token(file.owner_token_mut(), &matcher_main); @@ -66,6 +66,9 @@ impl<'a> Info<'a> { return Err(Error::Expired); } + // Get the password + let mut password = matcher_info.password(); + // Ensure a password is set when required ensure_password(&mut password, exists.has_password(), &matcher_main); diff --git a/cli/src/action/params.rs b/cli/src/action/params.rs index 478c082..97a44a8 100644 --- a/cli/src/action/params.rs +++ b/cli/src/action/params.rs @@ -42,8 +42,9 @@ impl<'a> Params<'a> { // Create a reqwest client let client = Client::new(); - // Parse the remote file based on the share URL + // Parse the remote file based on the share URL, derive the owner token from history let mut file = RemoteFile::parse_url(url, matcher_params.owner())?; + history_tool::derive_owner_token(&matcher_main, &mut file); // Ensure the owner token is set ensure_owner_token(file.owner_token_mut(), &matcher_main); diff --git a/cli/src/action/password.rs b/cli/src/action/password.rs index 745a0a2..87a4275 100644 --- a/cli/src/action/password.rs +++ b/cli/src/action/password.rs @@ -41,8 +41,9 @@ impl<'a> Password<'a> { // Create a reqwest client let client = Client::new(); - // Parse the remote file based on the share URL + // Parse the remote file based on the share URL, derive the owner token from history let mut file = RemoteFile::parse_url(url, matcher_password.owner())?; + history_tool::derive_owner_token(&matcher_main, &mut file); // Ensure the owner token is set ensure_owner_token(file.owner_token_mut(), &matcher_main); diff --git a/cli/src/history.rs b/cli/src/history.rs index 73111c1..3123056 100644 --- a/cli/src/history.rs +++ b/cli/src/history.rs @@ -198,6 +198,16 @@ impl History { &self.files } + /// Get a file from the history, based on the given remote file. + /// The file ID and host will be compared against all files in this history. + /// If multiple files exist within the history that are equal, only one is returned. + /// If no matching file was found, `None` is returned. + pub fn get_file(&self, file: &RemoteFile) -> Option<&RemoteFile> { + self.files.iter() + .filter(|f| f.id() == file.id() && f.host() == file.host()) + .next() + } + /// Garbage collect (remove) all files that have been expired, /// as defined by their `expire_at` property. /// diff --git a/cli/src/history_tool.rs b/cli/src/history_tool.rs index fec3309..e60cd72 100644 --- a/cli/src/history_tool.rs +++ b/cli/src/history_tool.rs @@ -78,3 +78,49 @@ pub fn remove(matcher_main: &MainMatcher, file: &RemoteFile) -> bool { } ok } + +/// Derive an owner token of a remote file from the current history. +/// The newly derived owner token will be set into the given borrowed remote file. +/// This method may be used to automatically derive the owner token for some file actions +/// requiring this token, to prevent the user from having to enter it manually. +/// +/// If the file already has an owner token set, +/// nothing will be derived and `false` is returned. +/// If an error occurred while deriving, +/// the error is printed and `false` is returned. +/// If there was no matching file in the history, +/// and no owner token could be derived, `false` is returned. +/// If in incognito mode, nothing is derived and `false` is returned. +/// +/// If an owner token was successfully derived from the history, +/// `true` is returned. +/// +/// Note, to check if an owner token is set in the remote file, +/// use the `file.has_owner_token()` method instead of the returned boolean from this method. +pub fn derive_owner_token(matcher_main: &MainMatcher, file: &mut RemoteFile) -> bool { + // Return if the remote file already has an owner token set, or if we're incognito + if file.has_owner_token() || matcher_main.incognito() { + return false; + } + + // Load the history + let history = match History::load_or_new(matcher_main.history()) { + Ok(history) => history, + Err(err) => { + print_error(err.context( + "Failed to derive file owner token from history, ignoring", + )); + return false; + } + }; + + // Find a matching file, grab and set the owner token if available + match history.get_file(file) { + Some(f) if f.has_owner_token() => { + file.set_owner_token(f.owner_token().cloned()); + println!("DEBUG: Owner token set from history"); + return true; + }, + _ => return false, + } +}