diff --git a/Cargo.lock b/Cargo.lock index 5236d59..b758b42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" diff --git a/fixtures/test-server-upload-dir-limit/config.toml b/fixtures/test-server-upload-dir-limit/config.toml new file mode 100644 index 0000000..53bd752 --- /dev/null +++ b/fixtures/test-server-upload-dir-limit/config.toml @@ -0,0 +1,9 @@ +[server] +address = "127.0.0.1:8000" +max_content_length = "10KB" +max_upload_dir_size = "20KB" +upload_path = "./upload" + +[paste] +default_extension = "txt" +duplicate_files = true diff --git a/fixtures/test-server-upload-dir-limit/test.sh b/fixtures/test-server-upload-dir-limit/test.sh new file mode 100755 index 0000000..a730b5d --- /dev/null +++ b/fixtures/test-server-upload-dir-limit/test.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +setup() { + truncate -s 9KB bigfile1 bigfile2 bigfile3 +} + +run_test() { + result=$(curl -s -F "file=@bigfile1" localhost:8000) + result=$(curl -s -F "file=@bigfile2" localhost:8000) + curl -s "$result" + + result=$(curl -s -F "file=@bigfile3" localhost:8000) + test "upload directory size limit exceeded" = "$result" +} + +teardown() { + rm bigfile* + rm -r upload +} diff --git a/src/config.rs b/src/config.rs index 3342995..52cb68d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,8 @@ pub struct ServerConfig { pub max_content_length: Byte, /// Storage path. pub upload_path: PathBuf, + /// Maximum upload directory size. + pub max_upload_dir_size: Option, /// Request timeout. #[serde(default, with = "humantime_serde")] pub timeout: Option, diff --git a/src/paste.rs b/src/paste.rs index 01da450..e95ebb8 100644 --- a/src/paste.rs +++ b/src/paste.rs @@ -4,12 +4,15 @@ use crate::header::ContentDisposition; use crate::util; use actix_web::{error, Error}; use awc::Client; -use std::convert::{TryFrom, TryInto}; use std::fs::{self, File}; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write}; use std::path::{Path, PathBuf}; use std::str; use std::sync::RwLock; +use std::{ + convert::{TryFrom, TryInto}, + ops::Add, +}; use url::Url; /// Type of the data to store. @@ -109,6 +112,21 @@ impl Paste { } } } + + if let Some(max_dir_size) = config.server.max_upload_dir_size { + let file_size = u64::try_from(self.data.len()).unwrap_or_default(); + let upload_dir = self.type_.get_path(&config.server.upload_path)?; + let current_size_of_upload_dir = util::get_dir_size(&upload_dir).map_err(|e| { + error::ErrorInternalServerError(format!("could not get directory size: {e}")) + })?; + let expected_size_of_upload_dir = current_size_of_upload_dir.add(file_size); + if expected_size_of_upload_dir > max_dir_size { + return Err(error::ErrorInsufficientStorage( + "upload directory size limit exceeded", + )); + } + } + let mut file_name = match PathBuf::from(file_name) .file_name() .and_then(|v| v.to_str()) diff --git a/src/util.rs b/src/util.rs index 11bac07..91a4313 100644 --- a/src/util.rs +++ b/src/util.rs @@ -131,6 +131,31 @@ pub fn safe_path_join, P: AsRef>(base: B, part: P) -> IoRes Ok(new_path) } +/// Returns the size of the directory at the given path. +/// +/// This function is recursive, and will calculate the size of all files and directories. +/// If a symlink is encountered, the size of the symlink itself is counted, not its target. +/// +/// Adopted from +pub fn get_dir_size(path: &Path) -> IoResult { + let path_metadata = path.symlink_metadata()?; + let mut size_in_bytes = 0; + if path_metadata.is_dir() { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let entry_metadata = entry.metadata()?; + if entry_metadata.is_dir() { + size_in_bytes += get_dir_size(&entry.path())?; + } else { + size_in_bytes += entry_metadata.len(); + } + } + } else { + size_in_bytes = path_metadata.len(); + } + Ok(size_in_bytes) +} + #[cfg(test)] mod tests { use super::*;