aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSven-Hendrik Haase <svenstaro@gmail.com>2020-09-24 03:44:58 +0000
committerGitHub <noreply@github.com>2020-09-24 03:44:58 +0000
commit4884c009d862a0692f145ec5c7dd485f2211da36 (patch)
tree0a5f92a12f3a67147700b88e51dd21f55dc00faf /src
parentBump deps (diff)
parentadd test for breadcrumbs (diff)
downloadminiserve-4884c009d862a0692f145ec5c7dd485f2211da36.tar.gz
miniserve-4884c009d862a0692f145ec5c7dd485f2211da36.zip
Merge pull request #378 from ahti/master
add title option (#335) and breadcrumb links in heading
Diffstat (limited to 'src')
-rw-r--r--src/args.rs5
-rw-r--r--src/listing.rs65
-rw-r--r--src/main.rs5
-rw-r--r--src/renderer.rs40
4 files changed, 92 insertions, 23 deletions
diff --git a/src/args.rs b/src/args.rs
index 8467a92..25a3503 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -99,6 +99,10 @@ struct CLIArgs {
/// because zip generation is done in memory and cannot be sent on the fly
#[structopt(short = "z", long = "enable-zip")]
enable_zip: bool,
+
+ /// Shown instead of host in page title and heading
+ #[structopt(short = "t", long = "title")]
+ title: Option<String>,
}
/// Checks wether an interface is valid, i.e. it can be parsed into an IP address
@@ -201,6 +205,7 @@ pub fn parse_args() -> crate::MiniserveConfig {
file_upload: args.file_upload,
tar_enabled: args.enable_tar,
zip_enabled: args.enable_zip,
+ title: args.title,
}
}
diff --git a/src/listing.rs b/src/listing.rs
index a70e237..d14a188 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -8,7 +8,7 @@ use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, CONTRO
use qrcodegen::{QrCode, QrCodeEcc};
use serde::Deserialize;
use std::io;
-use std::path::{Path, PathBuf};
+use std::path::{Component, Path, PathBuf};
use std::time::SystemTime;
use strum_macros::{Display, EnumString};
@@ -123,6 +123,21 @@ impl Entry {
}
}
+/// One entry in the path to the listed directory
+pub struct Breadcrumb {
+ /// Name of directory
+ pub name: String,
+
+ /// Link to get to directory, relative to listed directory
+ pub link: String,
+}
+
+impl Breadcrumb {
+ fn new(name: String, link: String) -> Self {
+ Breadcrumb { name, link }
+ }
+}
+
pub async fn file_handler(req: HttpRequest) -> Result<actix_files::NamedFile> {
let path = &req.app_data::<crate::MiniserveConfig>().unwrap().path;
actix_files::NamedFile::open(path).map_err(Into::into)
@@ -143,6 +158,7 @@ pub fn directory_listing(
upload_route: String,
tar_enabled: bool,
zip_enabled: bool,
+ title: Option<String>,
) -> Result<ServiceResponse, io::Error> {
use actix_web::dev::BodyEncoding;
let serve_path = req.path();
@@ -163,23 +179,52 @@ pub fn directory_listing(
}
let base = Path::new(serve_path);
- let random_route = format!("/{}", random_route.unwrap_or_default());
- let is_root = base.parent().is_none() || Path::new(&req.path()) == Path::new(&random_route);
+ let random_route_abs = format!("/{}", random_route.clone().unwrap_or_default());
+ let is_root = base.parent().is_none() || Path::new(&req.path()) == Path::new(&random_route_abs);
- let encoded_dir = match base.strip_prefix(random_route) {
+ let encoded_dir = match base.strip_prefix(random_route_abs) {
Ok(c_d) => Path::new("/").join(c_d),
Err(_) => base.to_path_buf(),
}
.display()
.to_string();
- let display_dir = {
+ let breadcrumbs = {
+ let title = title.unwrap_or_else(|| req.connection_info().host().into());
+
let decoded = percent_decode_str(&encoded_dir).decode_utf8_lossy();
- if is_root {
- decoded.to_string()
- } else {
- format!("{}/", decoded)
+
+ let mut res: Vec<Breadcrumb> = Vec::new();
+ let mut link_accumulator =
+ format!("/{}", random_route.map(|r| r + "/").unwrap_or_default());
+
+ let mut components = Path::new(&*decoded).components().peekable();
+
+ while let Some(c) = components.next() {
+ let name;
+
+ match c {
+ Component::RootDir => {
+ name = title.clone();
+ }
+ Component::Normal(s) => {
+ name = s.to_string_lossy().to_string();
+ link_accumulator
+ .push_str(&(utf8_percent_encode(&name, FRAGMENT).to_string() + "/"));
+ }
+ _ => unreachable!(),
+ };
+
+ res.push(Breadcrumb::new(
+ name,
+ if components.peek().is_some() {
+ link_accumulator.clone()
+ } else {
+ ".".to_string()
+ },
+ ));
}
+ res
};
let query_params = extract_query_parameters(req);
@@ -360,7 +405,7 @@ pub fn directory_listing(
&upload_route,
&favicon_route,
&encoded_dir,
- &display_dir,
+ breadcrumbs,
tar_enabled,
zip_enabled,
)
diff --git a/src/main.rs b/src/main.rs
index 7cda975..1830a70 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -79,6 +79,9 @@ pub struct MiniserveConfig {
/// If false, creation of zip archives is disabled
pub zip_enabled: bool,
+
+ /// Shown instead of host in page title and heading
+ pub title: Option<String>,
}
fn main() {
@@ -286,6 +289,7 @@ fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) {
let file_upload = conf.file_upload;
let tar_enabled = conf.tar_enabled;
let zip_enabled = conf.zip_enabled;
+ let title = conf.title.clone();
upload_route = if let Some(random_route) = conf.random_route.clone() {
format!("/{}/upload", random_route)
} else {
@@ -315,6 +319,7 @@ fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) {
u_r.clone(),
tar_enabled,
zip_enabled,
+ title.clone(),
)
})
.default_handler(web::to(error_404)),
diff --git a/src/renderer.rs b/src/renderer.rs
index 11fe8e3..43cb222 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -6,7 +6,7 @@ use std::time::SystemTime;
use strum::IntoEnumIterator;
use crate::archive::CompressionMethod;
-use crate::listing::{Entry, SortingMethod, SortingOrder};
+use crate::listing::{Breadcrumb, Entry, SortingMethod, SortingOrder};
use crate::themes::ColorScheme;
/// Renders the file listing
@@ -24,7 +24,7 @@ pub fn page(
upload_route: &str,
favicon_route: &str,
encoded_dir: &str,
- display_dir: &str,
+ breadcrumbs: Vec<Breadcrumb>,
tar_enabled: bool,
zip_enabled: bool,
) -> Markup {
@@ -37,10 +37,16 @@ pub fn page(
default_color_scheme,
);
+ let title_path = breadcrumbs
+ .iter()
+ .map(|el| el.name.clone())
+ .collect::<Vec<_>>()
+ .join("/");
+
html! {
(DOCTYPE)
html {
- (page_header(display_dir, color_scheme, file_upload, favicon_route, false))
+ (page_header(&title_path, color_scheme, file_upload, favicon_route))
body#drop-container {
@if file_upload {
div.drag-form {
@@ -52,7 +58,19 @@ pub fn page(
(color_scheme_selector(sort_method, sort_order, color_scheme, default_color_scheme, serve_path, show_qrcode))
div.container {
span#top { }
- h1.title { "Index of " (display_dir) }
+ h1.title {
+ @for el in breadcrumbs {
+ @if el.link == "." {
+ // wrapped in span so the text doesn't shift slightly when it turns into a link
+ span { (el.name) }
+ } @else {
+ a.directory href=(parametrized_link(&el.link, sort_method, sort_order, color_scheme, default_color_scheme)) {
+ (el.name)
+ }
+ }
+ "/"
+ }
+ }
div.toolbar {
@if tar_enabled || zip_enabled {
div.download {
@@ -446,7 +464,7 @@ fn css(color_scheme: ColorScheme) -> Markup {
}}
nav > div {{
position: relative;
- margin-left: 0.5rem;
+ margin-left: 0.5rem;
}}
nav p {{
padding: 0.5rem 1rem;
@@ -836,11 +854,10 @@ fn chevron_down() -> Markup {
/// Partial: page header
fn page_header(
- serve_path: &str,
+ title: &str,
color_scheme: ColorScheme,
file_upload: bool,
favicon_route: &str,
- is_error: bool,
) -> Markup {
html! {
head {
@@ -848,11 +865,8 @@ fn page_header(
meta http-equiv="X-UA-Compatible" content="IE=edge";
meta name="viewport" content="width=device-width, initial-scale=1";
link rel="icon" type="image/svg+xml" href={ "/" (favicon_route) };
- @if is_error {
- title { (serve_path) }
- } @else {
- title { "Index of " (serve_path) }
- }
+ title { (title) }
+
style { (css(color_scheme)) }
@if file_upload {
(PreEscaped(r#"
@@ -944,7 +958,7 @@ pub fn render_error(
html! {
(DOCTYPE)
html {
- (page_header(&error_code.to_string(), color_scheme, false, favicon_route, true))
+ (page_header(&error_code.to_string(), color_scheme, false, favicon_route))
body {
div.error {
p { (error_code.to_string()) }