aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSven-Hendrik Haase <svenstaro@gmail.com>2021-09-23 18:36:26 +0000
committerGitHub <noreply@github.com>2021-09-23 18:36:26 +0000
commitd5995e82cecd5138fa092694292444a02af1bba9 (patch)
tree833a6f473138418e2e275675de16c4463cd029a2 /src
parentRename Archlinux -> Arch Linux (diff)
parentcargo fmt (diff)
downloadminiserve-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.rs4
-rw-r--r--src/auth.rs22
-rw-r--r--src/config.rs4
-rw-r--r--src/listing.rs6
-rw-r--r--src/renderer.rs112
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())
+ }
+
}
}
}