diff options
author | Sven-Hendrik Haase <svenstaro@gmail.com> | 2022-09-20 00:31:13 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-20 00:31:13 +0000 |
commit | 5a68df14385c730d6087a845250d28adab3c3751 (patch) | |
tree | 62789ab21c8c6a41b2865ae4bf69aed3f87ac644 /src | |
parent | Add CHANGELOG for plain text READMEs (diff) | |
parent | Merge branch 'svenstaro:master' into restrict-upload-dir (diff) | |
download | miniserve-5a68df14385c730d6087a845250d28adab3c3751.tar.gz miniserve-5a68df14385c730d6087a845250d28adab3c3751.zip |
Merge pull request #858 from jonasdiemer/restrict-upload-dir
Added option restrict-upload-dir
Diffstat (limited to 'src')
-rw-r--r-- | src/args.rs | 17 | ||||
-rw-r--r-- | src/config.rs | 18 | ||||
-rw-r--r-- | src/errors.rs | 6 | ||||
-rw-r--r-- | src/file_upload.rs | 13 | ||||
-rw-r--r-- | src/renderer.rs | 8 |
5 files changed, 53 insertions, 9 deletions
diff --git a/src/args.rs b/src/args.rs index a8718bd..8757bc8 100644 --- a/src/args.rs +++ b/src/args.rs @@ -107,23 +107,28 @@ pub struct CliArgs { #[clap(short = 'q', long = "qrcode")] pub qrcode: bool, - /// Enable file uploading - #[clap(short = 'u', long = "upload-files")] - pub file_upload: bool, + /// Enable file uploading (and optionally specify for which directory) + #[clap(short = 'u', long = "upload-files", value_hint = ValueHint::FilePath, min_values = 0)] + pub allowed_upload_dir: Option<Vec<PathBuf>>, /// Enable creating directories - #[clap(short = 'U', long = "mkdir", requires = "file-upload")] + #[clap(short = 'U', long = "mkdir", requires = "allowed-upload-dir")] pub mkdir_enabled: bool, /// Specify uploadable media types - #[clap(arg_enum, short = 'm', long = "media-type", requires = "file-upload")] + #[clap( + arg_enum, + short = 'm', + long = "media-type", + requires = "allowed-upload-dir" + )] pub media_type: Option<Vec<MediaType>>, /// Directly specify the uploadable media type expression #[clap( short = 'M', long = "raw-media-type", - requires = "file-upload", + requires = "allowed-upload-dir", conflicts_with = "media-type" )] pub media_type_raw: Option<String>, diff --git a/src/config.rs b/src/config.rs index 5bcbd62..7ca0693 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,7 @@ use rustls_pemfile as pemfile; use crate::{ args::{CliArgs, MediaType}, auth::RequiredAuth, + file_upload::sanitize_path, }; /// Possible characters for random routes @@ -87,6 +88,9 @@ pub struct MiniserveConfig { /// Enable file upload pub file_upload: bool, + /// List of allowed upload directories + pub allowed_upload_dir: Vec<String>, + /// HTML accept attribute value pub uploadable_media_type: Option<String>, @@ -247,7 +251,19 @@ impl MiniserveConfig { overwrite_files: args.overwrite_files, show_qrcode: args.qrcode, mkdir_enabled: args.mkdir_enabled, - file_upload: args.file_upload, + file_upload: args.allowed_upload_dir.is_some(), + allowed_upload_dir: args + .allowed_upload_dir + .unwrap_or_default() + .iter() + .map(|x| { + sanitize_path(x, false) + .unwrap() + .to_str() + .unwrap() + .replace('\\', "/") + }) + .collect(), uploadable_media_type, tar_enabled: args.enable_tar, tar_gz_enabled: args.enable_tar_gz, diff --git a/src/errors.rs b/src/errors.rs index b2ed459..06569d3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -22,6 +22,10 @@ pub enum ContextualError { #[error("File already exists, and the overwrite_files option has not been set")] DuplicateFileError, + /// Upload not allowed + #[error("Upload not allowed to this directory")] + UploadForbiddenError, + /// Any error related to an invalid path (failed to retrieve entry name, unexpected entry type, etc) #[error("Invalid path\ncaused by: {0}")] InvalidPathError(String), @@ -88,6 +92,8 @@ impl ResponseError for ContextualError { Self::InsufficientPermissionsError(_) => StatusCode::FORBIDDEN, Self::InvalidHttpCredentials => StatusCode::UNAUTHORIZED, Self::InvalidHttpRequestError(_) => StatusCode::BAD_REQUEST, + Self::DuplicateFileError => StatusCode::FORBIDDEN, + Self::UploadForbiddenError => StatusCode::FORBIDDEN, _ => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/src/file_upload.rs b/src/file_upload.rs index 6643c68..cf214b8 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -171,6 +171,17 @@ pub async fn upload_file( ContextualError::IoError("Failed to resolve path served by miniserve".to_string(), e) })?; + // Disallow paths outside of allowed directories + let upload_allowed = conf.allowed_upload_dir.is_empty() + || conf + .allowed_upload_dir + .iter() + .any(|s| upload_path.starts_with(s)); + + if !upload_allowed { + return Err(ContextualError::UploadForbiddenError); + } + // Disallow the target path to go outside of the served directory // The target directory shouldn't be canonicalized when it gets passed to // handle_multipart so that it can check for symlinks if needed @@ -207,7 +218,7 @@ pub async fn upload_file( /// and optionally prevent traversing hidden directories. /// /// See the unit tests tests::test_sanitize_path* for examples -fn sanitize_path(path: &Path, traverse_hidden: bool) -> Option<PathBuf> { +pub fn sanitize_path(path: &Path, traverse_hidden: bool) -> Option<PathBuf> { let mut buf = PathBuf::new(); for comp in path.components() { diff --git a/src/renderer.rs b/src/renderer.rs index a17b55d..6aad5bd 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -40,6 +40,12 @@ pub fn page( let title_path = breadcrumbs_to_path_string(breadcrumbs); + let upload_allowed = conf.allowed_upload_dir.is_empty() + || conf + .allowed_upload_dir + .iter() + .any(|x| encoded_dir.starts_with(&format!("/{}", x))); + html! { (DOCTYPE) html { @@ -120,7 +126,7 @@ pub fn page( } } div.toolbar_box_group { - @if conf.file_upload { + @if conf.file_upload && upload_allowed { div.toolbar_box { form id="file_submit" action=(upload_action) method="POST" enctype="multipart/form-data" { p { "Select a file to upload or drag it anywhere into the window" } |