Repository: svenstaro/miniserve
Pull request #728 (2022-02-06 11:50:00)

Custom route prefix
Builds on and fixes #682 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09075bb..df2dd4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 
 ## [Unreleased] - ReleaseDate
-- Fix panic when using TLS in some instances [#670](https://github.com/svenstaro/miniserve/issues/670) (thanks @aliemjay)
 
 ## [0.18.0] - 2021-10-26
 - Add raw mode and raw mode footer display [#508](https://github.com/svenstaro/miniserve/pull/508) (thanks @Jikstra)
diff --git a/src/args.rs b/src/args.rs
index 7a6e41e..77925a2 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -60,8 +60,12 @@ pub struct CliArgs {
     )]
     pub auth: Vec<auth::RequiredAuth>,
 
+    /// Use a specific route prefix
+    #[clap(long = "route-prefix")]
+    pub route_prefix: Option<String>,
+
     /// Generate a random 6-hexdigit route
-    #[clap(long = "random-route")]
+    #[clap(long = "random-route", conflicts_with("route-prefix"))]
     pub random_route: bool,
 
     /// Do not follow symbolic links
diff --git a/src/config.rs b/src/config.rs
index ccff7e3..73fcec2 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -47,8 +47,8 @@ pub struct MiniserveConfig {
     /// Show hidden files
     pub show_hidden: bool,
 
-    /// Enable random route generation
-    pub random_route: Option<String>,
+    /// Route prefix; Either empty or prefixed with slash
+    pub route_prefix: String,
 
     /// Randomly generated favicon route
     pub favicon_route: String,
@@ -131,16 +131,16 @@ impl MiniserveConfig {
             ]
         };
 
-        let random_route = if args.random_route {
-            Some(nanoid::nanoid!(6, &ROUTE_ALPHABET))
-        } else {
-            None
+        let route_prefix = match (args.route_prefix, args.random_route) {
+            (Some(prefix), _) => format!("/{}", prefix.trim_matches('/')),
+            (_, true) => format!("/{}", nanoid::nanoid!(6, &ROUTE_ALPHABET)),
+            _ => "".to_owned(),
         };
 
         // Generate some random routes for the favicon and css so that they are very unlikely to conflict with
         // real files.
-        let favicon_route = nanoid::nanoid!(10, &ROUTE_ALPHABET);
-        let css_route = nanoid::nanoid!(10, &ROUTE_ALPHABET);
+        let favicon_route = format!("{}/{}", route_prefix, nanoid::nanoid!(10, &ROUTE_ALPHABET));
+        let css_route = format!("{}/{}", route_prefix, nanoid::nanoid!(10, &ROUTE_ALPHABET));
 
         let default_color_scheme = args.color_scheme;
         let default_color_scheme_dark = args.color_scheme_dark;
@@ -197,7 +197,7 @@ impl MiniserveConfig {
             path_explicitly_chosen,
             no_symlinks: args.no_symlinks,
             show_hidden: args.hidden,
-            random_route,
+            route_prefix,
             favicon_route,
             css_route,
             default_color_scheme,
diff --git a/src/listing.rs b/src/listing.rs
index 9e02598..05e800c 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -161,7 +161,7 @@ pub fn directory_listing(
     let serve_path = req.path();
 
     let base = Path::new(serve_path);
-    let random_route_abs = format!("/{}", conf.random_route.clone().unwrap_or_default());
+    let random_route_abs = format!("/{}", conf.route_prefix);
     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_abs) {
@@ -180,10 +180,8 @@ pub fn directory_listing(
         let decoded = percent_decode_str(&encoded_dir).decode_utf8_lossy();
 
         let mut res: Vec<Breadcrumb> = Vec::new();
-        let mut link_accumulator = match &conf.random_route {
-            Some(random_route) => format!("/{}/", random_route),
-            None => "/".to_string(),
-        };
+
+        let mut link_accumulator = format!("{}/", &conf.route_prefix);
 
         let mut components = Path::new(&*decoded).components().peekable();
 
diff --git a/src/main.rs b/src/main.rs
index 7f1944f..16feaf0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -165,10 +165,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> {
                 Some(_) => format!("https://{}", addr),
                 None => format!("http://{}", addr),
             })
-            .map(|url| match miniserve_config.random_route {
-                Some(ref random_route) => format!("{}/{}", url, random_route),
-                None => url,
-            })
+            .map(|url| format!("{}{}", url, miniserve_config.route_prefix))
             .collect::<Vec<_>>()
     };
 
@@ -189,13 +186,10 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> {
             .app_data(inside_config.clone())
             .wrap_fn(errors::error_page_middleware)
             .wrap(middleware::Logger::default())
-            .route(
-                &format!("/{}", inside_config.favicon_route),
-                web::get().to(favicon),
-            )
-            .route(&format!("/{}", inside_config.css_route), web::get().to(css))
+            .route(&inside_config.favicon_route, web::get().to(favicon))
+            .route(&inside_config.css_route, web::get().to(css))
             .service(
-                web::scope(inside_config.random_route.as_deref().unwrap_or(""))
+                web::scope(&inside_config.route_prefix)
                     .wrap(middleware::Condition::new(
                         !inside_config.auth.is_empty(),
                         actix_web::middleware::Compat::new(HttpAuthentication::basic(
diff --git a/src/renderer.rs b/src/renderer.rs
index c2c4cb5..544be42 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -25,10 +25,7 @@ pub fn page(
         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 upload_route = format!("{}/upload", &conf.route_prefix);
     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);
@@ -481,8 +478,8 @@ fn page_header(title: &str, file_upload: bool, favicon_route: &str, css_route: &
             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) };
-            link rel="stylesheet" href={ "/" (css_route) };
+            link rel="icon" type="image/svg+xml" href={ (favicon_route) };
+            link rel="stylesheet" href={ (css_route) };
 
             title { (title) }
 
@@ -578,7 +575,7 @@ pub fn render_error(
                         p { (error) }
                     }
                     // WARN don't expose random route!
-                    @if conf.random_route.is_none() {
+                    @if conf.route_prefix.is_empty() {
                         div.error-nav {
                             a.error-back href=(return_address) {
                                 "Go back to file listing"


Test patch:
diff --git a/tests/bind.rs b/tests/bind.rs
index 1c816f0..0fa914e 100644
--- a/tests/bind.rs
+++ b/tests/bind.rs
@@ -53,6 +53,7 @@ fn bind_ipv4_ipv6(
 #[case(&["-i", "0.0.0.0"])]
 #[case(&["-i", "::", "-i", "0.0.0.0"])]
 #[case(&["--random-route"])]
+#[case(&["--route-prefix", "/prefix"])]
 fn validate_printed_urls(tmpdir: TempDir, port: u16, #[case] args: &[&str]) -> Result<(), Error> {
     let mut child = Command::cargo_bin("miniserve")?
         .arg(tmpdir.path())
diff --git a/tests/serve_request.rs b/tests/serve_request.rs
index ec2c978..d66ffd4 100644
--- a/tests/serve_request.rs
+++ b/tests/serve_request.rs
@@ -260,3 +260,18 @@ fn serve_index_instead_of_404_in_spa_mode(
 
     Ok(())
 }
+
+#[rstest]
+#[case(server(&["--route-prefix", "foobar"]))]
+#[case(server(&["--route-prefix", "/foobar/"]))]
+fn serves_requests_with_route_prefix(#[case] server: TestServer) -> Result<(), Error> {
+    let url_without_route = server.url();
+    let status = reqwest::blocking::get(url_without_route)?.status();
+    assert_eq!(status, StatusCode::NOT_FOUND);
+
+    let url_with_route = server.url().join("foobar")?;
+    let status = reqwest::blocking::get(url_with_route)?.status();
+    assert_eq!(status, StatusCode::OK);
+
+    Ok(())
+}