diff options
author | Sven-Hendrik Haase <svenstaro@gmail.com> | 2021-09-23 18:36:26 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-23 18:36:26 +0000 |
commit | d5995e82cecd5138fa092694292444a02af1bba9 (patch) | |
tree | 833a6f473138418e2e275675de16c4463cd029a2 /src | |
parent | Rename Archlinux -> Arch Linux (diff) | |
parent | cargo fmt (diff) | |
download | miniserve-d5995e82cecd5138fa092694292444a02af1bba9.tar.gz miniserve-d5995e82cecd5138fa092694292444a02af1bba9.zip |
Merge pull request #508 from Jikstra/feat_raw_mode
Implement a raw rendering mode for recursive folder download
Diffstat (limited to 'src')
-rw-r--r-- | src/args.rs | 4 | ||||
-rw-r--r-- | src/auth.rs | 22 | ||||
-rw-r--r-- | src/config.rs | 4 | ||||
-rw-r--r-- | src/listing.rs | 6 | ||||
-rw-r--r-- | src/renderer.rs | 112 |
5 files changed, 125 insertions, 23 deletions
diff --git a/src/args.rs b/src/args.rs index ff8d92b..c610bbe 100644 --- a/src/args.rs +++ b/src/args.rs @@ -142,6 +142,10 @@ pub struct CliArgs { #[clap(short = 'F', long = "hide-version-footer")] pub hide_version_footer: bool, + /// If enabled, display a wget command to recursively download the current directory + #[clap(short = 'W', long = "show-wget-footer")] + pub show_wget_footer: bool, + /// Generate completion file for a shell #[clap(long = "print-completions", value_name = "shell", possible_values = &Shell::variants())] pub print_completions: Option<Shell>, diff --git a/src/auth.rs b/src/auth.rs index 0d97f11..82b407c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -42,7 +42,7 @@ pub struct RequiredAuth { } /// Return `true` if `basic_auth` is matches any of `required_auth` -pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &[RequiredAuth]) -> bool { +pub fn match_auth(basic_auth: &BasicAuthParams, required_auth: &[RequiredAuth]) -> bool { required_auth .iter() .any(|RequiredAuth { username, password }| { @@ -74,6 +74,9 @@ pub fn get_hash<T: Digest>(text: &str) -> Vec<u8> { hasher.update(text); hasher.finalize().to_vec() } +pub struct CurrentUser { + pub name: String, +} fn handle_auth(req: &HttpRequest) -> Result<(), ContextualError> { let required_auth = &req.app_data::<crate::MiniserveConfig>().unwrap().auth; @@ -84,8 +87,13 @@ fn handle_auth(req: &HttpRequest) -> Result<(), ContextualError> { } match BasicAuthParams::try_from_request(req) { - Ok(cred) => match match_auth(cred, required_auth) { - true => Ok(()), + Ok(cred) => match match_auth(&cred, required_auth) { + true => { + req.extensions_mut().insert(CurrentUser { + name: cred.username, + }); + Ok(()) + } false => Err(ContextualError::InvalidHttpCredentials), }, Err(_) => Err(ContextualError::RequireHttpCredentials), @@ -173,7 +181,7 @@ mod tests { ) { assert_eq!( match_auth( - BasicAuthParams { + &BasicAuthParams { username: param_username.to_owned(), password: param_password.to_owned(), }, @@ -214,7 +222,7 @@ mod tests { password: &str, ) { assert!(match_auth( - BasicAuthParams { + &BasicAuthParams { username: username.to_owned(), password: password.to_owned(), }, @@ -225,7 +233,7 @@ mod tests { #[rstest] fn test_multiple_auth_wrong_username(account_sample: Vec<RequiredAuth>) { assert_eq!(match_auth( - BasicAuthParams { + &BasicAuthParams { username: "unregistered user".to_owned(), password: "pwd0".to_owned(), }, @@ -248,7 +256,7 @@ mod tests { password: &str, ) { assert_eq!(match_auth( - BasicAuthParams { + &BasicAuthParams { username: username.to_owned(), password: password.to_owned(), }, diff --git a/src/config.rs b/src/config.rs index 86fe314..ce4e5d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -101,6 +101,9 @@ pub struct MiniserveConfig { /// If enabled, version footer is hidden pub hide_version_footer: bool, + /// If enabled, display a wget command to recursively download the current directory + pub show_wget_footer: bool, + /// If set, use provided rustls config for TLS #[cfg(feature = "tls")] pub tls_rustls_config: Option<rustls::ServerConfig>, @@ -192,6 +195,7 @@ impl MiniserveConfig { header: args.header, show_symlink_info: args.show_symlink_info, hide_version_footer: args.hide_version_footer, + show_wget_footer: args.show_wget_footer, tls_rustls_config: tls_rustls_server_config, }) } diff --git a/src/listing.rs b/src/listing.rs index ef4c8c8..9273025 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -12,6 +12,7 @@ use std::time::SystemTime; use strum_macros::{Display, EnumString}; use crate::archive::ArchiveMethod; +use crate::auth::CurrentUser; use crate::errors::{self, ContextualError}; use crate::renderer; use percent_encode_sets::PATH_SEGMENT; @@ -32,6 +33,7 @@ pub struct QueryParameters { pub path: Option<PathBuf>, pub sort: Option<SortingMethod>, pub order: Option<SortingOrder>, + pub raw: Option<bool>, qrcode: Option<String>, download: Option<ArchiveMethod>, } @@ -152,6 +154,9 @@ pub fn directory_listing( dir: &actix_files::Directory, req: &HttpRequest, ) -> io::Result<ServiceResponse> { + let extensions = req.extensions(); + let current_user: Option<&CurrentUser> = extensions.get::<CurrentUser>(); + use actix_web::dev::BodyEncoding; let conf = req.app_data::<crate::MiniserveConfig>().unwrap(); let serve_path = req.path(); @@ -374,6 +379,7 @@ pub fn directory_listing( breadcrumbs, &encoded_dir, conf, + current_user, ) .into_string(), ), diff --git a/src/renderer.rs b/src/renderer.rs index 7a7ed30..5a6d303 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -6,6 +6,7 @@ use maud::{html, Markup, PreEscaped, DOCTYPE}; use std::time::SystemTime; use strum::IntoEnumIterator; +use crate::auth::CurrentUser; use crate::listing::{Breadcrumb, Entry, QueryParameters, SortingMethod, SortingOrder}; use crate::{archive::ArchiveMethod, MiniserveConfig}; @@ -17,12 +18,19 @@ pub fn page( breadcrumbs: Vec<Breadcrumb>, encoded_dir: &str, conf: &MiniserveConfig, + current_user: Option<&CurrentUser>, ) -> Markup { + // If query_params.raw is true, we want render a minimal directory listing + if query_params.raw.is_some() && query_params.raw.unwrap() { + return raw(entries, is_root); + } + let upload_route = match conf.random_route { Some(ref random_route) => format!("/{}/upload", random_route), None => "/upload".to_string(), }; let (sort_method, sort_order) = (query_params.sort, query_params.order); + let upload_action = build_upload_action(&upload_route, encoded_dir, sort_method, sort_order); let title_path = breadcrumbs @@ -80,7 +88,7 @@ pub fn page( // wrapped in span so the text doesn't shift slightly when it turns into a link span { bdi { (el.name) } } } @else { - a href=(parametrized_link(&el.link, sort_method, sort_order)) { + a href=(parametrized_link(&el.link, sort_method, sort_order, false)) { bdi { (el.name) } } } @@ -120,22 +128,59 @@ pub fn page( tr { td colspan="3" { span.root-chevron { (chevron_left()) } - a.root href=(parametrized_link("../", sort_method, sort_order)) { + a.root href=(parametrized_link("../", sort_method, sort_order, false)) { "Parent directory" } } } } @for entry in entries { - (entry_row(entry, sort_method, sort_order)) + (entry_row(entry, sort_method, sort_order, false)) } } } a.back href="#top" { (arrow_up()) } - @if !conf.hide_version_footer { - (version_footer()) + div.footer { + @if conf.show_wget_footer { + (wget_footer(&title_path, current_user)) + } + @if !conf.hide_version_footer { + (version_footer()) + } + } + } + } + } + } +} + +/// Renders the file listing +pub fn raw(entries: Vec<Entry>, is_root: bool) -> Markup { + html! { + (DOCTYPE) + html { + body { + table { + thead { + th.name { "Name" } + th.size { "Size" } + th.date { "Last modification" } + } + tbody { + @if !is_root { + tr { + td colspan="3" { + a.root href=(parametrized_link("../", None, None, true)) { + ".." + } + } + } + } + @for entry in entries { + (entry_row(entry, None, None, true)) + } } } } @@ -146,12 +191,36 @@ pub fn page( // Partial: version footer fn version_footer() -> Markup { html! { - p.footer { - (format!("{}/{}", crate_name!(), crate_version!())) - } + div.version { + (format!("{}/{}", crate_name!(), crate_version!())) + } } } +fn wget_footer(title_path: &str, current_user: Option<&CurrentUser>) -> Markup { + let count = { + let count_slashes = title_path.matches('/').count(); + if count_slashes > 0 { + count_slashes - 1 + } else { + 0 + } + }; + + let user_params = if let Some(user) = current_user { + format!(" --ask-password --user {}", user.name) + } else { + "".to_string() + }; + + return html! { + div.downloadDirectory { + p { "Download folder:" } + div.cmd { (format!("wget -r -c -nH -np --cut-dirs={} -R \"index.html*\"{} \"http://{}/?raw=true\"", count, user_params, title_path)) } + } + }; +} + /// Build the action of the upload form fn build_upload_action( upload_route: &str, @@ -232,7 +301,7 @@ fn archive_button( } else { format!( "{}&download={}", - parametrized_link("", sort_method, sort_order,), + parametrized_link("", sort_method, sort_order, false), archive_method ) }; @@ -260,14 +329,19 @@ fn parametrized_link( link: &str, sort_method: Option<SortingMethod>, sort_order: Option<SortingOrder>, + raw: bool, ) -> String { + if raw { + return format!("{}?raw=true", make_link_with_trailing_slash(link)); + } + if let Some(method) = sort_method { if let Some(order) = sort_order { let parametrized_link = format!( "{}?sort={}&order={}", make_link_with_trailing_slash(link), method, - order + order, ); return parametrized_link; @@ -315,6 +389,7 @@ fn entry_row( entry: Entry, sort_method: Option<SortingMethod>, sort_order: Option<SortingOrder>, + raw: bool, ) -> Markup { html! { tr { @@ -322,13 +397,13 @@ fn entry_row( p { @if entry.is_dir() { @if let Some(symlink_dest) = entry.symlink_info { - a.symlink href=(parametrized_link(&entry.link, sort_method, sort_order)) { + a.symlink href=(parametrized_link(&entry.link, sort_method, sort_order, raw)) { (entry.name) "/" span.symlink-symbol { } a.directory {(symlink_dest) "/"} } }@else { - a.directory href=(parametrized_link(&entry.link, sort_method, sort_order)) { + a.directory href=(parametrized_link(&entry.link, sort_method, sort_order, raw)) { (entry.name) "/" } } @@ -345,9 +420,11 @@ fn entry_row( } } - @if let Some(size) = entry.size { - span.mobile-info.size { - (size) + @if !raw { + @if let Some(size) = entry.size { + span.mobile-info.size { + (size) + } } } } @@ -509,7 +586,10 @@ pub fn render_error( } } @if !conf.hide_version_footer { - (version_footer()) + p.footer { + (version_footer()) + } + } } } |