diff options
-rw-r--r-- | src/args.rs | 7 | ||||
-rw-r--r-- | src/config.rs | 4 | ||||
-rw-r--r-- | src/listing.rs | 8 | ||||
-rw-r--r-- | src/renderer.rs | 2 | ||||
-rw-r--r-- | tests/archive.rs | 28 | ||||
-rw-r--r-- | tests/serve_request.rs | 35 |
6 files changed, 83 insertions, 1 deletions
diff --git a/src/args.rs b/src/args.rs index 7e3668b..3fb0ede 100644 --- a/src/args.rs +++ b/src/args.rs @@ -293,6 +293,13 @@ pub struct CliArgs { /// Enable README.md rendering in directories #[arg(long, env = "MINISERVE_README")] pub readme: bool, + + /// Disable indexing + /// + /// This will prevent directory listings from being generated + /// and return an error instead. + #[arg(short = 'I', long, env = "MINISERVE_DISABLE_INDEXING")] + pub disable_indexing: 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 43414b2..3643502 100644 --- a/src/config.rs +++ b/src/config.rs @@ -146,6 +146,9 @@ pub struct MiniserveConfig { /// If enabled, render the readme from the current directory pub readme: bool, + /// If enabled, indexing is disabled. + pub disable_indexing: bool, + /// If set, use provided rustls config for TLS #[cfg(feature = "tls")] pub tls_rustls_config: Option<rustls::ServerConfig>, @@ -311,6 +314,7 @@ impl MiniserveConfig { hide_theme_selector: args.hide_theme_selector, show_wget_footer: args.show_wget_footer, readme: args.readme, + disable_indexing: args.disable_indexing, tls_rustls_config: tls_rustls_server_config, compress_response: args.compress_response, }) diff --git a/src/listing.rs b/src/listing.rs index 855abef..64cdaca 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -161,6 +161,14 @@ pub fn directory_listing( let current_user: Option<&CurrentUser> = extensions.get::<CurrentUser>(); let conf = req.app_data::<crate::MiniserveConfig>().unwrap(); + if conf.disable_indexing { + return Ok(ServiceResponse::new( + req.clone(), + HttpResponse::NotFound() + .content_type(mime::TEXT_PLAIN_UTF_8) + .body("File not found."), + )); + } let serve_path = req.path(); let base = Path::new(serve_path); diff --git a/src/renderer.rs b/src/renderer.rs index 3b62704..b691bca 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -687,7 +687,7 @@ pub fn render_error( p { (error) } } // WARN don't expose random route! - @if conf.route_prefix.is_empty() { + @if conf.route_prefix.is_empty() && !conf.disable_indexing { div.error-nav { a.error-back href=(return_address) { "Go back to file listing" diff --git a/tests/archive.rs b/tests/archive.rs index b8def22..e6d0263 100644 --- a/tests/archive.rs +++ b/tests/archive.rs @@ -56,3 +56,31 @@ fn test_tar_archives(#[with(&["-g"])] server: TestServer) -> Result<(), Error> { Ok(()) } + +#[rstest] +#[case(server(&["--disable-indexing", "--enable-tar-gz", "--enable-tar", "--enable-zip"]))] +fn archives_are_disabled_when_indexing_disabled(#[case] server: TestServer) -> Result<(), Error> { + // Ensure the links to the archives are not present + let body = reqwest::blocking::get(server.url())?; + let parsed = Document::from_read(body)?; + assert!(parsed + .find(Text) + .all(|x| x.text() != "Download .tar.gz" && x.text() != "Download .tar")); + + // Try to download anyway, ensure it's forbidden + // We assert for not found to make sure we aren't leaking information about directories that do exist. + assert_eq!( + reqwest::blocking::get(server.url().join("?download=tar_gz")?)?.status(), + StatusCode::NOT_FOUND + ); + assert_eq!( + reqwest::blocking::get(server.url().join("?download=tar")?)?.status(), + StatusCode::NOT_FOUND + ); + assert_eq!( + reqwest::blocking::get(server.url().join("?download=zip")?)?.status(), + StatusCode::NOT_FOUND + ); + + Ok(()) +} diff --git a/tests/serve_request.rs b/tests/serve_request.rs index ac4360e..b7359c3 100644 --- a/tests/serve_request.rs +++ b/tests/serve_request.rs @@ -321,3 +321,38 @@ fn serves_requests_static_file_check( Ok(()) } + +#[rstest] +#[case(server(&["--disable-indexing"]))] +fn serves_no_directory_if_indexing_disabled(#[case] server: TestServer) -> Result<(), Error> { + let body = reqwest::blocking::get(server.url())?; + assert_eq!(body.status(), StatusCode::NOT_FOUND); + let parsed = Document::from_read(body)?; + + assert!(parsed + .find(|x: &Node| x.text() == FILES[0]) + .next() + .is_none()); + assert!(parsed + .find(|x: &Node| x.text() == DIRECTORIES[0]) + .next() + .is_none()); + assert!(parsed + .find(|x: &Node| x.text() == "404 Not Found") + .next() + .is_some()); + assert!(parsed + .find(|x: &Node| x.text() == "File not found.") + .next() + .is_some()); + + Ok(()) +} + +#[rstest] +#[case(server(&["--disable-indexing"]))] +fn serves_file_requests_when_indexing_disabled(#[case] server: TestServer) -> Result<(), Error> { + reqwest::blocking::get(format!("{}{}", server.url(), FILES[0]))?.error_for_status()?; + + Ok(()) +} |