浏览代码

Check and limit file upload sizes

timvisee 7 年之前
父节点
当前提交
5dbafa6dbd
共有 5 个文件被更改,包括 84 次插入9 次删除
  1. 2 3
      IDEAS.md
  2. 5 0
      api/src/config.rs
  3. 1 0
      api/src/lib.rs
  4. 54 6
      cli/src/action/upload.rs
  5. 22 0
      cli/src/util.rs

+ 2 - 3
IDEAS.md

@@ -1,7 +1,6 @@
 # First release
-- Only allow file extension renaming on upload with `-f` flag
 - Check for file expiry everywhere
-- Soft limit uploads to 1GB and 2GB
+- On download, mention a wrong or missing password with a HTTP 401 response
 - Remember all uploaded files, make files listable
 - Incognito mode, to not remember files `--incognito`
 - Automatically get owner token, from file history when setting password
@@ -12,7 +11,7 @@
 
 # Other ideas
 - Box errors
-- On download, mention a wrong or missing password with a HTTP 401 response
+- Only allow file extension renaming on upload with `-f` flag
 - Implement error handling everywhere properly
 - Quick upload/download without `upload` or `download` subcommands?
 - Flag to explicitly delete file after download

+ 5 - 0
api/src/config.rs

@@ -0,0 +1,5 @@
+/// The recommended maximum upload size in bytes.
+pub const UPLOAD_SIZE_MAX_RECOMMENDED: u64 = 1_073_741_824;
+
+/// The maximum upload size in bytes.
+pub const UPLOAD_SIZE_MAX: u64 = 2_147_483_648;

+ 1 - 0
api/src/lib.rs

@@ -16,6 +16,7 @@ pub extern crate url;
 
 pub mod action;
 mod api;
+pub mod config;
 pub mod crypto;
 mod ext;
 pub mod file;

+ 54 - 6
cli/src/action/upload.rs

@@ -1,3 +1,4 @@
+use std::fs::File;
 use std::path::Path;
 use std::sync::{Arc, Mutex};
 
@@ -5,17 +6,24 @@ use clap::ArgMatches;
 use failure::{err_msg, Fail};
 use ffsend_api::action::params::ParamsDataBuilder;
 use ffsend_api::action::upload::Upload as ApiUpload;
+use ffsend_api::config::{UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_RECOMMENDED};
 use ffsend_api::reqwest::Client;
 
-use cmd::matcher::{
-    Matcher,
-    upload::UploadMatcher,
-};
+use cmd::matcher::{Matcher, MainMatcher, UploadMatcher};
 use error::ActionError;
 use progress::ProgressBar;
-use util::open_url;
+use util::{
+    ErrorHintsBuilder,
+    format_bytes,
+    open_url,
+    print_error,
+    print_error_msg,
+    prompt_yes,
+    quit,
+    quit_error_msg,
+};
 #[cfg(feature = "clipboard")]
-use util::{print_error, set_clipboard};
+use util::set_clipboard;
 
 /// A file upload action.
 pub struct Upload<'a> {
@@ -34,12 +42,52 @@ impl<'a> Upload<'a> {
     // TODO: create a trait for this method
     pub fn invoke(&self) -> Result<(), ActionError> {
         // Create the command matchers
+        let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
         let matcher_upload = UploadMatcher::with(self.cmd_matches).unwrap();
 
         // Get API parameters
         let path = Path::new(matcher_upload.file()).to_path_buf();
         let host = matcher_upload.host();
 
+        // TODO: ensure the file exists and is accessible
+
+        // Get the file size to warn about large files
+        if let Ok(size) = File::open(&path)
+            .and_then(|f| f.metadata())
+            .map(|m| m.len())
+        {
+            if size > UPLOAD_SIZE_MAX && !matcher_main.force() {
+                // The file is too large, show an error and quit
+                quit_error_msg(
+                    format!(
+                        "The file size is {}, bigger than the maximum allowed of {}",
+                        format_bytes(size),
+                        format_bytes(UPLOAD_SIZE_MAX),
+                    ),
+                    ErrorHintsBuilder::default()
+                        .force(true)
+                        .verbose(false)
+                        .build()
+                        .unwrap(),
+                );
+            } else if size > UPLOAD_SIZE_MAX_RECOMMENDED && !matcher_main.force() {
+                // The file is larger than the recommended maximum, warn
+                eprintln!(
+                    "The file size is {}, bigger than the recommended maximum of {}",
+                    format_bytes(size),
+                    format_bytes(UPLOAD_SIZE_MAX_RECOMMENDED),
+                );
+
+                // Prompt the user to continue, quit if the user answered no
+                if !prompt_yes("Continue uploading?", Some(true), &matcher_main) {
+                    println!("Upload cancelled");
+                    quit();
+                }
+            }
+        } else {
+            print_error_msg("Failed to check the file size, ignoring");
+        }
+
         // Create a reqwest client
         let client = Client::new();
 

+ 22 - 0
cli/src/util.rs

@@ -48,6 +48,15 @@ pub fn print_error<E: Fail>(err: E) {
     }
 }
 
+/// Print the given error message in a proper format for the user,
+/// with it's causes.
+pub fn print_error_msg<S>(err: S)
+    where
+        S: AsRef<str> + Display + Debug + Sync + Send + 'static
+{
+    print_error(err_msg(err).compat());
+}
+
 /// Quit the application regularly.
 pub fn quit() -> ! {
     exit(0);
@@ -393,3 +402,16 @@ pub fn ensure_owner_token(
         }
     }
 }
+
+/// Format the given number of bytes readable for humans.
+pub fn format_bytes(bytes: u64) -> String {
+    let bytes = bytes as f64;
+    let kb = 1024f64;
+    match bytes {
+        bytes if bytes >= kb.powf(4_f64) => format!("{:.*} TiB", 2, bytes / kb.powf(4_f64)),
+        bytes if bytes >= kb.powf(3_f64) => format!("{:.*} GiB", 2, bytes / kb.powf(3_f64)),
+        bytes if bytes >= kb.powf(2_f64) => format!("{:.*} MiB", 2, bytes / kb.powf(2_f64)),
+        bytes if bytes >= kb => format!("{:.*} KiB", 2, bytes / kb),
+        _ => format!("{:.*} B", 0, bytes),
+    }
+}