aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/args.rs17
-rw-r--r--src/config.rs18
-rw-r--r--src/errors.rs6
-rw-r--r--src/file_upload.rs13
-rw-r--r--src/listing.rs17
-rw-r--r--src/renderer.rs10
6 files changed, 66 insertions, 15 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/listing.rs b/src/listing.rs
index 7477599..fbee8de 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -9,6 +9,7 @@ use actix_web::{HttpMessage, HttpRequest, HttpResponse};
use bytesize::ByteSize;
use comrak::{markdown_to_html, ComrakOptions};
use percent_encoding::{percent_decode_str, utf8_percent_encode};
+use regex::Regex;
use serde::Deserialize;
use strum_macros::{Display, EnumString};
@@ -224,6 +225,7 @@ pub fn directory_listing(
let mut entries: Vec<Entry> = Vec::new();
let mut readme: Option<(String, String)> = None;
+ let readme_rx: Regex = Regex::new("^readme([.](md|txt))?$").unwrap();
for entry in dir.path.read_dir()? {
if dir.is_visible(&entry) || conf.show_hidden {
@@ -274,13 +276,18 @@ pub fn directory_listing(
last_modification_date,
symlink_dest,
));
- if conf.readme && file_name.to_lowercase() == "readme.md" {
+ if conf.readme && readme_rx.is_match(&file_name.to_lowercase()) {
+ let ext = file_name.split('.').last().unwrap().to_lowercase();
readme = Some((
file_name.to_string(),
- markdown_to_html(
- &std::fs::read_to_string(entry.path())?,
- &ComrakOptions::default(),
- ),
+ if ext == "md" {
+ markdown_to_html(
+ &std::fs::read_to_string(entry.path())?,
+ &ComrakOptions::default(),
+ )
+ } else {
+ format!("<pre>{}</pre>", &std::fs::read_to_string(entry.path())?)
+ },
));
}
}
diff --git a/src/renderer.rs b/src/renderer.rs
index c8958fe..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" }
@@ -235,7 +241,7 @@ fn qr_code_svg(url: impl AsRef<str>, margin: usize) -> Result<String, QRCodeErro
let qr = QRBuilder::new(url.as_ref().into())
.ecl(consts::QR_EC_LEVEL)
.build()?;
- let svg = SvgBuilder::new().margin(margin).build_qr(qr);
+ let svg = SvgBuilder::default().margin(margin).to_str(&qr);
Ok(svg)
}