From aed776ac49cb44705463d9e43c070dc56adaaae3 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Mon, 10 Jun 2024 00:38:45 +0200 Subject: Remove explicit dependency on http We now use the one supplied by actix-web. --- src/args.rs | 2 +- src/config.rs | 2 +- src/listing.rs | 6 ++++-- src/renderer.rs | 3 +-- 4 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 95c8bff..20be079 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,8 +1,8 @@ use std::net::IpAddr; use std::path::PathBuf; +use actix_web::http::header::{HeaderMap, HeaderName, HeaderValue}; use clap::{Parser, ValueEnum, ValueHint}; -use http::header::{HeaderMap, HeaderName, HeaderValue}; use crate::auth; use crate::listing::{SortingMethod, SortingOrder}; diff --git a/src/config.rs b/src/config.rs index 3643502..5a0037b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,8 +5,8 @@ use std::{ path::PathBuf, }; +use actix_web::http::header::HeaderMap; use anyhow::{anyhow, Context, Result}; -use http::HeaderMap; #[cfg(feature = "tls")] use rustls_pemfile as pemfile; diff --git a/src/listing.rs b/src/listing.rs index e24b41c..6c67051 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -3,7 +3,9 @@ use std::io; use std::path::{Component, Path}; use std::time::SystemTime; -use actix_web::{dev::ServiceResponse, web::Query, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{ + dev::ServiceResponse, http::Uri, web::Query, HttpMessage, HttpRequest, HttpResponse, +}; use bytesize::ByteSize; use clap::ValueEnum; use comrak::{markdown_to_html, ComrakOptions}; @@ -173,7 +175,7 @@ pub fn directory_listing( let base = Path::new(serve_path); let random_route_abs = format!("/{}", conf.route_prefix); let abs_uri = { - let res = http::Uri::builder() + let res = Uri::builder() .scheme(req.connection_info().scheme()) .authority(req.connection_info().host()) .path_and_query(req.uri().to_string()) diff --git a/src/renderer.rs b/src/renderer.rs index 3935d98..7896dc3 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,6 +1,6 @@ use std::time::SystemTime; -use actix_web::http::StatusCode; +use actix_web::http::{StatusCode, Uri}; use chrono::{DateTime, Local}; use chrono_humanize::Humanize; use clap::{crate_name, crate_version, ValueEnum}; @@ -9,7 +9,6 @@ use fast_qr::{ qr::QRCodeError, QRBuilder, }; -use http::Uri; use maud::{html, Markup, PreEscaped, DOCTYPE}; use strum::{Display, IntoEnumIterator}; -- cgit v1.2.3 From 81df80c1c91f77847da9c8a4a71df51b8526392c Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Mon, 10 Jun 2024 01:06:16 +0200 Subject: Bump rustls to v0.23 --- src/config.rs | 21 ++++++--------------- src/main.rs | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 5a0037b..f468365 100644 --- a/src/config.rs +++ b/src/config.rs @@ -223,24 +223,15 @@ impl MiniserveConfig { let key_file = &mut BufReader::new( File::open(&tls_key).context(format!("Couldn't access TLS key {tls_key:?}"))?, ); - let cert_chain = pemfile::certs(cert_file).context("Reading cert file")?; - let key = pemfile::read_all(key_file) + let cert_chain = pemfile::certs(cert_file) + .map(|cert| cert.expect("Invalid certificate in certificate chain")) + .collect(); + let private_key = pemfile::private_key(key_file) .context("Reading private key file")? - .into_iter() - .find_map(|item| match item { - pemfile::Item::RSAKey(key) - | pemfile::Item::PKCS8Key(key) - | pemfile::Item::ECKey(key) => Some(key), - _ => None, - }) - .ok_or_else(|| anyhow!("No supported private key in file"))?; + .expect("No private key found"); let server_config = rustls::ServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert( - cert_chain.into_iter().map(rustls::Certificate).collect(), - rustls::PrivateKey(key), - )?; + .with_single_cert(cert_chain, private_key)?; Some(server_config) } else { None diff --git a/src/main.rs b/src/main.rs index aa40585..7b04f7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -228,7 +228,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), StartupError> { #[cfg(feature = "tls")] let srv = match &miniserve_config.tls_rustls_config { - Some(tls_config) => srv.listen_rustls(listener, tls_config.clone()), + Some(tls_config) => srv.listen_rustls_0_23(listener, tls_config.clone()), None => srv.listen(listener), }; -- cgit v1.2.3 From 873d485053f296b928f7bdd29a38e206a64222cb Mon Sep 17 00:00:00 2001 From: Zakhary Kaplan Date: Wed, 26 Jun 2024 18:05:00 -0400 Subject: Fix typos in CLI --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 20be079..dd09f65 100644 --- a/src/args.rs +++ b/src/args.rs @@ -162,7 +162,7 @@ pub struct CliArgs { /// The provided path is not a physical file system path. Instead, it's relative to the serve /// dir. For instance, if the serve dir is '/home/hello', set this to '/upload' to allow /// uploading to '/home/hello/upload'. - /// When specified via environment variable, a path always neesd to the specified. + /// When specified via environment variable, a path always needs to be specified. #[arg(short = 'u', long = "upload-files", value_hint = ValueHint::FilePath, num_args(0..=1), value_delimiter(','), env = "MINISERVE_ALLOWED_UPLOAD_DIR")] pub allowed_upload_dir: Option>, -- cgit v1.2.3 From 52c5cc09fce55ed2c3c3d98343e31b5da6f340af Mon Sep 17 00:00:00 2001 From: Gaurav Atreya Date: Sat, 13 Jul 2024 22:33:40 -0400 Subject: Don't show mkdir option when the directory is not upload allowed Fixes #1249 --- src/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/renderer.rs b/src/renderer.rs index 7896dc3..9c60dcc 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -117,7 +117,7 @@ pub fn page( } } } - @if conf.mkdir_enabled { + @if conf.mkdir_enabled && upload_allowed { div.toolbar_box { form id="mkdir" action=(mkdir_action) method="POST" enctype="multipart/form-data" { p { "Specify a directory name to create" } -- cgit v1.2.3 From e9241ed0199c4f2e76d3f4143f131e01342c79e5 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Sat, 20 Jul 2024 14:58:00 +0200 Subject: Bump deps --- src/file_op.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/file_op.rs b/src/file_op.rs index e22e3e9..18bdcbe 100644 --- a/src/file_op.rs +++ b/src/file_op.rs @@ -61,7 +61,7 @@ async fn handle_multipart( allow_hidden_paths: bool, allow_symlinks: bool, ) -> Result { - let field_name = field.name().to_string(); + let field_name = field.name().expect("No name field found").to_string(); match tokio::fs::metadata(&path).await { Err(_) => Err(RuntimeError::InsufficientPermissionsError( @@ -143,12 +143,16 @@ async fn handle_multipart( }; } - let filename = field.content_disposition().get_filename().ok_or_else(|| { - RuntimeError::ParseError( - "HTTP header".to_string(), - "Failed to retrieve the name of the file to upload".to_string(), - ) - })?; + let filename = field + .content_disposition() + .expect("No content-disposition field found") + .get_filename() + .ok_or_else(|| { + RuntimeError::ParseError( + "HTTP header".to_string(), + "Failed to retrieve the name of the file to upload".to_string(), + ) + })?; let filename_path = sanitize_path(Path::new(&filename), allow_hidden_paths) .ok_or_else(|| RuntimeError::InvalidPathError("Invalid file name to upload".to_string()))?; -- cgit v1.2.3 From 1fdbbd5f5759544f6e9c28c435d2e925fb7fbd61 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 12 Sep 2024 02:49:34 +0200 Subject: Fix lints --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 7b04f7c..1434a0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,9 +52,8 @@ fn main() -> Result<()> { let miniserve_config = MiniserveConfig::try_from_args(args)?; - run(miniserve_config).map_err(|e| { + run(miniserve_config).inspect_err(|e| { errors::log_error_chain(e.to_string()); - e })?; Ok(()) -- cgit v1.2.3 From 17f4b0243bd8a66bc1578067685b10bdbef3fe04 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:56:53 +0800 Subject: Make URL encoding fully WHATWG-compliant --- src/listing.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/listing.rs b/src/listing.rs index 6c67051..88d4f69 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -19,7 +19,7 @@ use crate::auth::CurrentUser; use crate::errors::{self, RuntimeError}; use crate::renderer; -use self::percent_encode_sets::PATH_SEGMENT; +use self::percent_encode_sets::COMPONENT; /// "percent-encode sets" as defined by WHATWG specs: /// https://url.spec.whatwg.org/#percent-encoded-bytes @@ -28,7 +28,17 @@ mod percent_encode_sets { const BASE: &AsciiSet = &CONTROLS.add(b'%'); pub const QUERY: &AsciiSet = &BASE.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); pub const PATH: &AsciiSet = &QUERY.add(b'?').add(b'`').add(b'{').add(b'}'); - pub const PATH_SEGMENT: &AsciiSet = &PATH.add(b'/').add(b'\\'); + pub const USERINFO: &AsciiSet = &PATH + .add(b'/') + .add(b':') + .add(b';') + .add(b'=') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^'); + pub const COMPONENT: &AsciiSet = &USERINFO.add(b'$').add(b'%').add(b'&').add(b'+').add(b','); } /// Query parameters used by listing APIs @@ -216,7 +226,7 @@ pub fn directory_listing( Component::Normal(s) => { name = s.to_string_lossy().to_string(); link_accumulator - .push_str(&(utf8_percent_encode(&name, PATH_SEGMENT).to_string() + "/")); + .push_str(&(utf8_percent_encode(&name, COMPONENT).to_string() + "/")); } _ => name = "".to_string(), }; @@ -255,7 +265,7 @@ pub fn directory_listing( .and_then(|path| std::fs::read_link(path).ok()) .map(|path| path.to_string_lossy().into_owned()); let file_url = base - .join(utf8_percent_encode(&file_name, PATH_SEGMENT).to_string()) + .join(utf8_percent_encode(&file_name, COMPONENT).to_string()) .to_string_lossy() .to_string(); -- cgit v1.2.3 From 8577561843ecdfbf6159813bebf8049beb8aa58f Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Sun, 22 Sep 2024 02:33:06 +0800 Subject: Remove non-compliant `BASE` set --- src/listing.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/listing.rs b/src/listing.rs index 88d4f69..9e916cc 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -25,8 +25,7 @@ use self::percent_encode_sets::COMPONENT; /// https://url.spec.whatwg.org/#percent-encoded-bytes mod percent_encode_sets { use percent_encoding::{AsciiSet, CONTROLS}; - const BASE: &AsciiSet = &CONTROLS.add(b'%'); - pub const QUERY: &AsciiSet = &BASE.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); + pub const QUERY: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); pub const PATH: &AsciiSet = &QUERY.add(b'?').add(b'`').add(b'{').add(b'}'); pub const USERINFO: &AsciiSet = &PATH .add(b'/') -- cgit v1.2.3 From e6b50aea4c1355f6df8b03e0bf9f74b2860807af Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Sun, 22 Sep 2024 02:38:43 +0800 Subject: Add missing `|` to `USERINFO` set --- src/listing.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/listing.rs b/src/listing.rs index 9e916cc..a9d2e3a 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -36,7 +36,8 @@ mod percent_encode_sets { .add(b'[') .add(b'\\') .add(b']') - .add(b'^'); + .add(b'^') + .add(b'|'); pub const COMPONENT: &AsciiSet = &USERINFO.add(b'$').add(b'%').add(b'&').add(b'+').add(b','); } -- cgit v1.2.3 From bc6e1f9faab1d22b4f3eee2892dc5e21bac7c62c Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Fri, 11 Oct 2024 05:21:41 +0200 Subject: Prefix OVERWRITE_FILES env var Fixes #1457. --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index dd09f65..ce6e4e2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -195,7 +195,7 @@ pub struct CliArgs { pub media_type_raw: Option, /// Enable overriding existing files during file upload - #[arg(short = 'o', long = "overwrite-files", env = "OVERWRITE_FILES")] + #[arg(short = 'o', long = "overwrite-files", env = "MINISERVE_OVERWRITE_FILES")] pub overwrite_files: bool, /// Enable uncompressed tar archive generation -- cgit v1.2.3 From c74f03e2914fe42f7e243011d2285a534435122e Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Fri, 11 Oct 2024 21:46:07 +0200 Subject: Fix formatting --- src/args.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index ce6e4e2..9ac6772 100644 --- a/src/args.rs +++ b/src/args.rs @@ -195,7 +195,11 @@ pub struct CliArgs { pub media_type_raw: Option, /// Enable overriding existing files during file upload - #[arg(short = 'o', long = "overwrite-files", env = "MINISERVE_OVERWRITE_FILES")] + #[arg( + short = 'o', + long = "overwrite-files", + env = "MINISERVE_OVERWRITE_FILES" + )] pub overwrite_files: bool, /// Enable uncompressed tar archive generation -- cgit v1.2.3 From dad77b656c130f43d9de66f0f28e49fa1418f291 Mon Sep 17 00:00:00 2001 From: adamnemecek Date: Thu, 2 Jan 2025 22:58:57 -0800 Subject: Use Self where possible --- src/archive.rs | 24 ++++++++++++------------ src/config.rs | 2 +- src/listing.rs | 4 ++-- src/pipe.rs | 2 +- src/renderer.rs | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/archive.rs b/src/archive.rs index b8ba4d4..da79ef8 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -28,27 +28,27 @@ pub enum ArchiveMethod { impl ArchiveMethod { pub fn extension(self) -> String { match self { - ArchiveMethod::TarGz => "tar.gz", - ArchiveMethod::Tar => "tar", - ArchiveMethod::Zip => "zip", + Self::TarGz => "tar.gz", + Self::Tar => "tar", + Self::Zip => "zip", } .to_string() } pub fn content_type(self) -> String { match self { - ArchiveMethod::TarGz => "application/gzip", - ArchiveMethod::Tar => "application/tar", - ArchiveMethod::Zip => "application/zip", + Self::TarGz => "application/gzip", + Self::Tar => "application/tar", + Self::Zip => "application/zip", } .to_string() } pub fn is_enabled(self, tar_enabled: bool, tar_gz_enabled: bool, zip_enabled: bool) -> bool { match self { - ArchiveMethod::TarGz => tar_gz_enabled, - ArchiveMethod::Tar => tar_enabled, - ArchiveMethod::Zip => zip_enabled, + Self::TarGz => tar_gz_enabled, + Self::Tar => tar_enabled, + Self::Zip => zip_enabled, } } @@ -69,9 +69,9 @@ impl ArchiveMethod { { let dir = dir.as_ref(); match self { - ArchiveMethod::TarGz => tar_gz(dir, skip_symlinks, out), - ArchiveMethod::Tar => tar_dir(dir, skip_symlinks, out), - ArchiveMethod::Zip => zip_dir(dir, skip_symlinks, out), + Self::TarGz => tar_gz(dir, skip_symlinks, out), + Self::Tar => tar_dir(dir, skip_symlinks, out), + Self::Zip => zip_dir(dir, skip_symlinks, out), } } } diff --git a/src/config.rs b/src/config.rs index f468365..984f873 100644 --- a/src/config.rs +++ b/src/config.rs @@ -269,7 +269,7 @@ impl MiniserveConfig { .transpose()? .unwrap_or_default(); - Ok(MiniserveConfig { + Ok(Self { verbose: args.verbose, path: args.path.unwrap_or_else(|| PathBuf::from(".")), port, diff --git a/src/listing.rs b/src/listing.rs index a9d2e3a..bd82c94 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -121,7 +121,7 @@ impl Entry { last_modification_date: Option, symlink_info: Option, ) -> Self { - Entry { + Self { name, entry_type, link, @@ -153,7 +153,7 @@ pub struct Breadcrumb { impl Breadcrumb { fn new(name: String, link: String) -> Self { - Breadcrumb { name, link } + Self { name, link } } } diff --git a/src/pipe.rs b/src/pipe.rs index 51f094a..45ada8b 100644 --- a/src/pipe.rs +++ b/src/pipe.rs @@ -17,7 +17,7 @@ pub struct Pipe { impl Pipe { /// Wrap the given sender in a `Pipe`. pub fn new(destination: Sender>) -> Self { - Pipe { + Self { dest: destination, bytes: BytesMut::new(), } diff --git a/src/renderer.rs b/src/renderer.rs index 9c60dcc..28ca2d9 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -326,10 +326,10 @@ pub enum ThemeSlug { impl ThemeSlug { pub fn css(&self) -> &str { match self { - ThemeSlug::Squirrel => grass::include!("data/themes/squirrel.scss"), - ThemeSlug::Archlinux => grass::include!("data/themes/archlinux.scss"), - ThemeSlug::Zenburn => grass::include!("data/themes/zenburn.scss"), - ThemeSlug::Monokai => grass::include!("data/themes/monokai.scss"), + Self::Squirrel => grass::include!("data/themes/squirrel.scss"), + Self::Archlinux => grass::include!("data/themes/archlinux.scss"), + Self::Zenburn => grass::include!("data/themes/zenburn.scss"), + Self::Monokai => grass::include!("data/themes/monokai.scss"), } } -- cgit v1.2.3 From 317bd6a5d42a83c9c5e874788282a6e76f638211 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Thu, 6 Feb 2025 00:03:05 +0100 Subject: add read-only webdav support --- src/args.rs | 6 ++++ src/config.rs | 4 +++ src/errors.rs | 3 ++ src/main.rs | 51 ++++++++++++++++++++++++++++++++-- src/webdav_fs.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/webdav_fs.rs (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 9ac6772..922e78b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -309,6 +309,12 @@ pub struct CliArgs { /// and return an error instead. #[arg(short = 'I', long, env = "MINISERVE_DISABLE_INDEXING")] pub disable_indexing: bool, + + /// Enable read-only WebDAV support (PROPFIND requests) + /// + /// Currently incompatible with -P|--no-symlinks (see https://github.com/messense/dav-server-rs/issues/37) + #[arg(long, env = "MINISERVE_ENABLE_WEBDAV", conflicts_with = "no_symlinks")] + pub enable_webdav: bool, } /// Checks whether an interface is valid, i.e. it can be parsed into an IP address diff --git a/src/config.rs b/src/config.rs index 984f873..5d2d7e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -149,6 +149,9 @@ pub struct MiniserveConfig { /// If enabled, indexing is disabled. pub disable_indexing: bool, + /// If enabled, respond to WebDAV requests (read-only). + pub webdav_enabled: bool, + /// If set, use provided rustls config for TLS #[cfg(feature = "tls")] pub tls_rustls_config: Option, @@ -306,6 +309,7 @@ impl MiniserveConfig { show_wget_footer: args.show_wget_footer, readme: args.readme, disable_indexing: args.disable_indexing, + webdav_enabled: args.enable_webdav, tls_rustls_config: tls_rustls_server_config, compress_response: args.compress_response, }) diff --git a/src/errors.rs b/src/errors.rs index 21f8f12..99c15ff 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -24,6 +24,9 @@ Please set an explicit serve path like: `miniserve /my/path`")] /// In case miniserve was invoked with --no-symlinks but the serve path is a symlink #[error("The -P|--no-symlinks option was provided but the serve path '{0}' is a symlink")] NoSymlinksOptionWithSymlinkServePath(String), + + #[error("The --enable-webdav option was provided, but the serve path '{0}' is a file")] + WebdavWithFileServePath(String), } #[derive(Debug, Error)] diff --git a/src/main.rs b/src/main.rs index 1434a0c..ccf611c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,18 @@ use std::time::Duration; use actix_files::NamedFile; use actix_web::{ dev::{fn_service, ServiceRequest, ServiceResponse}, - http::header::ContentType, + guard, + http::{header::ContentType, Method}, middleware, web, App, HttpRequest, HttpResponse, Responder, }; use actix_web_httpauth::middleware::HttpAuthentication; use anyhow::Result; use clap::{crate_version, CommandFactory, Parser}; use colored::*; +use dav_server::{ + actix::{DavRequest, DavResponse}, + DavConfig, DavHandler, DavMethodSet, +}; use fast_qr::QRBuilder; use log::{error, warn}; @@ -27,9 +32,11 @@ mod file_utils; mod listing; mod pipe; mod renderer; +mod webdav_fs; use crate::config::MiniserveConfig; use crate::errors::{RuntimeError, StartupError}; +use crate::webdav_fs::RestrictedFs; static STYLESHEET: &str = grass::include!("data/style.scss"); @@ -88,6 +95,12 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), StartupError> { )); } + if miniserve_config.webdav_enabled && miniserve_config.path.is_file() { + return Err(StartupError::WebdavWithFileServePath( + miniserve_config.path.to_string_lossy().to_string(), + )); + } + let inside_config = miniserve_config.clone(); let canon_path = miniserve_config @@ -307,7 +320,9 @@ fn configure_header(conf: &MiniserveConfig) -> middleware::DefaultHeaders { /// This is where we configure the app to serve an index file, the file listing, or a single file. fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) { let dir_service = || { - let mut files = actix_files::Files::new("", &conf.path); + // use routing guard so propfind and options requests fall through to the webdav handler + let mut files = actix_files::Files::new("", &conf.path) + .guard(guard::Any(guard::Get()).or(guard::Head())); // Use specific index file if one was provided. if let Some(ref index_file) = conf.index { @@ -376,6 +391,38 @@ fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) { // Handle directories app.service(dir_service()); } + + if conf.webdav_enabled { + let fs = RestrictedFs::new(&conf.path, conf.show_hidden); + + let dav_server = DavHandler::builder() + .filesystem(fs) + .methods(DavMethodSet::WEBDAV_RO) + .hide_symlinks(conf.no_symlinks) + .strip_prefix(conf.route_prefix.to_owned()) + .build_handler(); + + app.app_data(web::Data::new(dav_server.clone())); + + app.service( + // actix requires tail segment to be named, even if unused + web::resource("/{tail}*") + .guard( + guard::Any(guard::Options()) + .or(guard::Method(Method::from_bytes(b"PROPFIND").unwrap())), + ) + .to(dav_handler), + ); + } +} + +async fn dav_handler(req: DavRequest, davhandler: web::Data) -> DavResponse { + if let Some(prefix) = req.prefix() { + let config = DavConfig::new().strip_prefix(prefix); + davhandler.handle_with(config, req.request).await.into() + } else { + davhandler.handle(req.request).await.into() + } } async fn error_404(req: HttpRequest) -> Result { diff --git a/src/webdav_fs.rs b/src/webdav_fs.rs new file mode 100644 index 0000000..63c9f94 --- /dev/null +++ b/src/webdav_fs.rs @@ -0,0 +1,83 @@ +//! Helper types and functions to allow configuring hidden files visibility +//! for WebDAV handlers + +use dav_server::{davpath::DavPath, fs::*, localfs::LocalFs}; +use futures::{future::ready, StreamExt, TryFutureExt}; +use std::path::{Component, Path}; + +/// A dav_server local filesystem backend that can be configured to deny access +/// to files and directories with names starting with a dot. +#[derive(Clone)] +pub struct RestrictedFs { + local: Box, + show_hidden: bool, +} + +impl RestrictedFs { + /// Creates a new RestrictedFs serving the local path at "base". + /// If "show_hidden" is false, access to hidden files is prevented. + pub fn new>(base: P, show_hidden: bool) -> Box { + let local = LocalFs::new(base, false, false, false); + Box::new(RestrictedFs { local, show_hidden }) + } +} + +/// true if any normal component of path either starts with dot or can't be turned into a str +fn path_has_hidden_components(path: &DavPath) -> bool { + path.as_pathbuf().components().any(|c| match c { + Component::Normal(name) => name.to_str().map_or(true, |s| s.starts_with('.')), + _ => false, + }) +} + +impl DavFileSystem for RestrictedFs { + fn open<'a>( + &'a self, + path: &'a DavPath, + options: OpenOptions, + ) -> FsFuture<'a, Box> { + if !path_has_hidden_components(path) || self.show_hidden { + self.local.open(path, options) + } else { + Box::pin(ready(Err(FsError::NotFound))) + } + } + + fn read_dir<'a>( + &'a self, + path: &'a DavPath, + meta: ReadDirMeta, + ) -> FsFuture<'a, FsStream>> { + if self.show_hidden { + self.local.read_dir(path, meta) + } else if !path_has_hidden_components(path) { + Box::pin(self.local.read_dir(path, meta).map_ok(|stream| { + let dyn_stream: FsStream> = Box::pin(stream.filter(|entry| { + ready(match entry { + Ok(ref e) => !e.name().starts_with(b"."), + _ => false, + }) + })); + dyn_stream + })) + } else { + Box::pin(ready(Err(FsError::NotFound))) + } + } + + fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box> { + if !path_has_hidden_components(path) || self.show_hidden { + self.local.metadata(path) + } else { + Box::pin(ready(Err(FsError::NotFound))) + } + } + + fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box> { + if !path_has_hidden_components(path) || self.show_hidden { + self.local.symlink_metadata(path) + } else { + Box::pin(ready(Err(FsError::NotFound))) + } + } +} -- cgit v1.2.3 From 48c436aab1e5587a155bc677d0481626c940e6a0 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 6 Feb 2025 04:14:26 +0100 Subject: Make clippy happy --- src/listing.rs | 7 ++----- src/webdav_fs.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/listing.rs b/src/listing.rs index bd82c94..d908e23 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -274,10 +274,7 @@ pub fn directory_listing( if conf.no_symlinks && is_symlink { continue; } - let last_modification_date = match metadata.modified() { - Ok(date) => Some(date), - Err(_) => None, - }; + let last_modification_date = metadata.modified().ok(); if metadata.is_dir() { entries.push(Entry::new( @@ -298,7 +295,7 @@ pub fn directory_listing( symlink_dest, )); if conf.readme && readme_rx.is_match(&file_name.to_lowercase()) { - let ext = file_name.split('.').last().unwrap().to_lowercase(); + let ext = file_name.split('.').next_back().unwrap().to_lowercase(); readme = Some(( file_name.to_string(), if ext == "md" { diff --git a/src/webdav_fs.rs b/src/webdav_fs.rs index 63c9f94..cf434ba 100644 --- a/src/webdav_fs.rs +++ b/src/webdav_fs.rs @@ -25,7 +25,7 @@ impl RestrictedFs { /// true if any normal component of path either starts with dot or can't be turned into a str fn path_has_hidden_components(path: &DavPath) -> bool { path.as_pathbuf().components().any(|c| match c { - Component::Normal(name) => name.to_str().map_or(true, |s| s.starts_with('.')), + Component::Normal(name) => name.to_str().is_none_or(|s| s.starts_with('.')), _ => false, }) } -- cgit v1.2.3 From 7b8c339ca3ffc12c621fc95545f138453e854332 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Thu, 6 Feb 2025 05:25:10 +0100 Subject: move favicon and css to stable, non-random routes --- src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 5d2d7e8..953bba1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -196,13 +196,13 @@ impl MiniserveConfig { // Otherwise, we should apply route_prefix to static files. let (favicon_route, css_route) = if args.random_route { ( - format!("/{}", nanoid::nanoid!(10, &ROUTE_ALPHABET)), - format!("/{}", nanoid::nanoid!(10, &ROUTE_ALPHABET)), + "/__miniserve_internal/favicon.svg".into(), + "/__miniserve_internal/style.css".into(), ) } else { ( - format!("{}/{}", route_prefix, nanoid::nanoid!(10, &ROUTE_ALPHABET)), - format!("{}/{}", route_prefix, nanoid::nanoid!(10, &ROUTE_ALPHABET)), + format!("{}/{}", route_prefix, "__miniserve_internal/favicon.ico"), + format!("{}/{}", route_prefix, "__miniserve_internal/style.css"), ) }; -- cgit v1.2.3 From 01811e26dbbdee82ac2070f747a6b5f8f0baba8f Mon Sep 17 00:00:00 2001 From: Flat Date: Thu, 6 Feb 2025 13:58:51 -0500 Subject: Add date pill and sort links for mobile views --- src/renderer.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/renderer.rs b/src/renderer.rs index 28ca2d9..ca49413 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -521,7 +521,12 @@ fn entry_row( @if !raw { @if let Some(size) = entry.size { span.mobile-info.size { - (maud::display(size)) + (build_link("size", &format!("{}", size), sort_method, sort_order)) + } + } + @if let Some(modification_timer) = humanize_systemtime(entry.last_modification_date) { + span.mobile-info.history { + (build_link("date", &modification_timer, sort_method, sort_order)) } } } -- cgit v1.2.3